Dynamic Federation: Tunneling Local GraphQL Subgraphs into Production Supergraphs

Quick answer
Dynamic Federation: Tunneling Local GraphQL Subgraphs : localhost tunnel answer
A localhost tunnel gives your local app a public HTTPS URL without opening router ports, which is useful for demos, QA, mobile testing, and provider callbacks.
How do I expose localhost without opening ports?
Use a reverse HTTPS tunnel. Your machine connects outbound to the tunnel service, and the public URL forwards requests back to your local app.
When should I use a localhost tunnel?
Use one for webhook testing, OAuth callbacks, client demos, QA previews, mobile device checks, and short-lived development reviews.
You shouldn’t have to spin up 50 microservices on your laptop just to test a single GraphQL schema change. This article breaks down the architecture of subgraph tunnels—how to seamlessly stitch your local code into a live cloud supergraph without the overhead of running the full stack locally.
1. The Paradigm Shift: From Monolithic APIs to Federated Supergraphs
GraphQL emerged as the standard answer to over-fetching and under-fetching, giving clients a single unified endpoint. As organisations scaled, a monolithic GraphQL server became an evolutionary bottleneck. Teams collided on deployments, schema conflicts were a daily occurrence, and a single service’s instability could take down the entire graph.
Apollo Federation addressed this at an architectural level. By decomposing a monolithic schema into domain-specific, independently deployable subgraphs, different engineering teams can own their respective slices of the API. A composition process stitches those subgraph SDL files into a single supergraph. A high-performance gateway or router sits at the edge, receiving client queries, building a query plan, and dispatching parallel requests to the underlying subgraphs.
Federation 2—the current canonical specification—introduced a cleaner shared-ownership model, enhanced type merging, and new composition hints for catching errors earlier. Subgraph schemas opt in by applying the @link directive to the schema type. Federation-specific directives—@key, @shareable, @provides, @requires, @interfaceObject, and others—encode the relationships between types across service boundaries. Composition validates those contracts statically and produces a supergraph SDL artifact consumed by the router at runtime.
Apollo Router Core is the reference runtime for this architecture. Written in Rust, it is engineered around three design principles: correctness (strict conformance to the GraphQL and Federation specifications), reliability (predictable latency, memory footprint, and crash-freedom), and safe experimentation (runtime feature flags and extensibility hooks that leave existing queries untouched).
Where Federation solved the organisational scaling problem in production, it created a new bottleneck in the development loop.
2. The Local Development Bottleneck
In a federated architecture with ten, thirty, or fifty distinct subgraphs, the developer experience degrades rapidly. Adding a field to the Inventory subgraph requires confidence that the change composes cleanly and that cross-service queries—those spanning Products, Users, and Inventory—still resolve correctly.
Three escape valves are typically reached for, each with real costs.
Running the full stack locally. Even with Docker Compose, resource exhaustion is a near-certainty. CPU throttling, OOM kills, and port conflicts compound quickly as the service count rises. For most engineers, this stops being viable past a handful of subgraphs.
Mocking missing subgraphs. A developer can run their single service locally and stub out the other forty-nine. The risk is drift. Mocks are a snapshot; staging and production are moving targets. Bugs that exist at the real join points between services aren’t caught until CI runs—or until after the merge.
Pushing to a staging environment. This preserves fidelity but collapses the feedback loop. Waiting for a full CI/CD pipeline to rebuild and redeploy a container in order to test a schema field rename is a productivity tax with outsized impact on iteration speed.
The underlying problem is isolation: a developer’s laptop is disconnected from the live, interconnected graph running in the cloud. Subgraph tunneling exists to dissolve that barrier.
3. Dynamic Subgraph Ingress: The Tunneling Pattern
Dynamic subgraph ingress is an architectural pattern that lets a developer run a single subgraph locally while a tunneling agent registers it with—and routes live traffic from—a cloud-based staging supergraph. The developer achieves full integration against the real composed graph without running any of the other services.
The data flow at query time is straightforward.
A client—an application or a developer in Apollo Sandbox—sends a query to the staging supergraph router. The router parses the operation and builds a query plan. For fields resolved by cloud-hosted subgraphs (Users, Reviews), it fetches from those staging clusters as normal. For any field owned by the subgraph currently under development (Inventory), it routes the sub-operation through a secure tunnel to localhost:4000 on the developer’s machine. The local process executes resolvers, hits local or remote databases, and returns the payload up the tunnel. The router aggregates all partial results and returns a unified response to the client.
From the client’s perspective, the response is indistinguishable from a fully cloud-hosted supergraph. From the developer’s perspective, the local process has full debuggability—breakpoints, variable inspection, stack traces—against live, composed traffic.
4. Core Architecture: Three Components
A complete subgraph tunnel implementation requires three coordinated components.
4.1 The Local Subgraph
This is a standard Federation-compatible GraphQL server running on the developer’s machine. Any language and framework that produces a valid subgraph SDL works: Node.js with @apollo/subgraph, Python with Strawberry, Go with gqlgen, Kotlin with DGS, and so on. Hot-module replacement or equivalent live-reload keeps schema changes immediately visible without a process restart.
The server must expose an HTTP port. That port is the target the tunnel will proxy traffic to.
4.2 The Secure Tunneling Agent
The tunneling agent creates a persistent, authenticated connection between the local machine and a public relay endpoint, bypassing NAT and corporate firewalls. Two production-grade options are in common use.
ngrok opens a multiplexed HTTP/2 stream to ngrok’s relay infrastructure and surfaces a stable public URL (e.g. https://dev-inventory.ngrok.app). Inbound HTTP requests hitting that URL are proxied over the tunnel to the designated local port. Authentication at the tunnel layer—ngrok supports OAuth 2.0, OpenID Connect, and mTLS—should always be configured when exposing a development server that has access to any real data or credentials.
Cloudflare Tunnel (cloudflared) operates on the same principle. A cloudflared daemon establishes outbound connections to Cloudflare’s edge network. Traffic arriving at a configured Cloudflare hostname is routed back through those connections to the local origin. Because the connection is outbound-only, no inbound firewall rules are required.
Both tools support webhook-style inspection UIs that let developers see the raw HTTP request and response flowing over the tunnel—useful for debugging subgraph protocol details.
4.3 The Dynamic Cloud Router
This is the most architecturally complex piece. The cloud-based router must be made aware of the tunnel URL and must substitute it for the internal staging URL of the target subgraph.
In a static configuration, the supergraph SDL encodes the routing URL for each subgraph as a join__Graph directive. The router reads those URLs at startup and sends sub-operations accordingly. For a tunnel to work, one of two things must happen: the composition must be updated to embed the tunnel URL for the target subgraph, or the router must be extended to override the URL at request time without recomposing.
Both approaches are valid for different use cases. Their trade-offs are explored in the next two sections.
5. Step-by-Step Implementation
The following is the canonical four-step sequence for wiring a local subgraph into a staging supergraph.
Step 1: Start the Local Subgraph
npm run dev --port 4000
# → Server running at http://localhost:4000/graphql
Confirm the server is returning a valid subgraph SDL by introspecting the endpoint or using a GraphQL IDE.
Step 2: Open the Reverse Tunnel
ngrok http 4000
# → Forwarding https://dev-inventory.ngrok.app -> localhost:4000
The public URL is now live. Any HTTP POST to https://dev-inventory.ngrok.app/graphql reaches your local resolver.
Step 3: Override the Subgraph Routing URL
With the Rover CLI, a supergraph.yaml configuration file can mix subgraph sources. An existing staging graph reference can be used to pull all other subgraphs from the registry while overriding the inventory entry’s routing_url and schema.subgraph_url to point at the tunnel:
federation_version: =2.12.0
subgraphs:
products:
routing_url: http://products.staging.svc.cluster.local
schema:
graphref: my-supergraph@staging
subgraph: products
users:
routing_url: http://users.staging.svc.cluster.local
schema:
graphref: my-supergraph@staging
subgraph: users
inventory:
routing_url: https://dev-inventory.ngrok.app/graphql
schema:
subgraph_url: https://dev-inventory.ngrok.app/graphql
Rover’s subgraph mirroring feature (introduced in a recent Rover release) can inherit routing URLs and schemas from an existing studio graph ref, making it straightforward to spin up a local supergraph without maintaining a full supergraph config from scratch—only the override for the subgraph under development needs to be specified explicitly.
Running rover dev starts a local router that queries across the mixed set of subgraphs, giving the developer a hybrid local/remote development environment without touching the shared staging cluster.
Step 4: End-to-End Validation
Point your frontend application or Apollo Sandbox at the local router endpoint (defaulting to http://localhost:4000). Execute any query that crosses subgraph boundaries. The developer’s terminal should receive the sub-operation for the locally-tunnelled subgraph. The router assembles all partial results and returns the unified response.
6. Shared Environments: Header-Based Dynamic Ingress
The approach above has a critical operational flaw if applied to the shared cloud staging router rather than a local router: updating the staging router’s subgraph URL to point at Developer A’s laptop means that Developer B’s requests—and QA’s automated test suite—are also routed to Developer A’s machine. When Developer A closes their laptop, the entire shared staging environment breaks.
Header-based dynamic ingress solves this by keeping per-developer routing entirely within the scope of individual requests rather than global router configuration.
The Mechanism
A developer attaches a custom header to their requests:
x-dev-routing: inventory=https://dev-inventory.ngrok.app
The router inspects this header and, only for this specific request, overrides the outbound URL for the inventory subgraph during execution. All other requests continue routing to the internal staging cluster.
Implementation with Rhai Scripts
Apollo Router supports Rhai scripting for in-memory manipulation of headers, cookies, and router context. Rhai scripts hook into the router’s request-handling lifecycle through named entry points: router_service, supergraph_service, execution_service, and subgraph_service. The subgraph_service hook is called once per subgraph request within a query plan—if a plan fans out to three subgraphs, the hook fires three times, each receiving the name of the target subgraph.
A Rhai script implementing header-based routing override:
fn subgraph_service(service, subgraph) {
let request_callback = |request| {
let routing_header = request.headers["x-dev-routing"];
if routing_header != () {
// Parse "subgraphName=https://tunnel-url" pairs
let parts = routing_header.split("=");
if parts.len() == 2 && parts[0].trim() == subgraph {
request.subgraph.uri = parts[1].trim();
}
}
};
service.map_request(request_callback);
}
Apollo’s published benchmarks show Rhai script overhead at approximately 100 µs at p95, making it an appropriate choice for this use case. For logic that cannot be expressed in Rhai—complex JWT parsing, calls to external services, database lookups—an external coprocessor communicating with the router over HTTP is the supported alternative, though coprocessor overhead averages around 350 µs per stage and scales with subgraph fan-out.
Coprocessors require a GraphOS Enterprise plan. Rhai scripts run on all router tiers, including the open-source Apollo Router Core binary.
Isolation Properties
With header-based routing in place, multiple developers can simultaneously tunnel different subgraphs into the same staging router. Each developer’s requests carry their own routing header; the Rhai script applies overrides only to matching requests. From the staging router’s perspective, the graph is always composed and available. A developer going offline affects only their own requests.
7. Ecosystem: Tooling That Supports These Workflows
Apollo GraphOS and Rover CLI
Rover is Apollo’s command-line tool for managing federated schemas. rover dev starts a local router instance—downloaded automatically the first time—that fetches subgraph schemas from a GraphOS Studio graph ref and stitches them with one or more locally-running subgraphs. The router’s default port is 4000.
The supergraph.yaml format that rover supergraph compose and rover dev consume supports three schema source types simultaneously: local .graphql files, live subgraph introspection URLs, and graph refs pointing at schemas already published to a GraphOS Studio variant. A single config can mix all three, which is exactly what a tunnelling workflow needs.
Recent Rover releases added subgraph mirroring, which inherits all subgraph routing URLs and schemas from an existing Studio graph ref without requiring a manually maintained supergraph.yaml. Only the locally-overridden subgraph needs an explicit entry.
The current LTS release of Apollo Router is v2.10, shipped in December 2025, pairing with Federation v2.12. Router v1.x reached end of support on 31 March 2026. Teams still on v1.x should have migrated by now; no subgraph changes are required for teams already on Federation v2.
GraphQL Hive
GraphQL Hive, maintained by The Guild, is a fully open-source, MIT-licensed alternative to Apollo GraphOS. It provides a schema registry, composition validation, usage analytics, and its own gateway options (Hive Gateway in JavaScript and Hive Router in Rust). The registry publishes composed supergraphs to a high-availability CDN backed by Cloudflare, from which gateways poll for updates.
Hive supports branched schema environments (development, staging, production targets are created by default) and schema promotion between those targets without republishing subgraphs. Teams can publish a tunnel URL as the routing_url for a subgraph to the development target while leaving the staging and production targets intact.
On 12 March 2025 Hive migrated from per-target Registry Access Tokens to organisation-level Access Tokens, which can be scoped to specific projects, targets, or service names. Tooling referencing the old --registry.accessToken flag now also requires a --target argument specifying the slug or UUID.
Hive Gateway and Hive Router
Hive Gateway (Node.js) and Hive Router (Rust) both poll the Hive CDN for the composed supergraph artifact. Because both support standard Federation composition, subgraph tunnel URLs work the same way: publish a tunnel URL as the routing URL for a development-target subgraph, and the gateway routes accordingly.
8. Security and Operational Considerations
Tunnel Authentication
Exposing a local development server to the internet via a public URL is inherently risky. A malicious actor who discovers the URL could POST arbitrary GraphQL operations to the local process, which typically runs with developer-level credentials and access to local environment variables, databases, or secrets.
Enforce authentication at the tunnel layer, not only at the application level. Both ngrok and cloudflared support OAuth 2.0 and mTLS enforcement at the relay point. The cloud router should be configured to include a shared secret or client certificate in requests forwarded through the tunnel, which the local tunnel agent validates before passing traffic to the application port.
Never expose a tunnel URL that reaches a local process with write access to production or staging databases without mTLS or equivalent credential verification.
Introspection and Schema Exposure
When a local subgraph is tunnelled into a staging supergraph, the supergraph’s schema and operation policies apply at the router edge. However, if the local subgraph’s introspection is enabled without restriction, a caller who knows the tunnel URL directly can introspect the schema outside the supergraph’s access controls.
Apollo Router v1.0 and later ship with introspection disabled by default in non-dev mode. Verify that the local subgraph’s introspection configuration matches the security posture appropriate for the data it exposes.
Nullability and Partial Failures
Local development environments are inherently volatile. A developer hits a breakpoint, restarts the server mid-request, or introduces a compilation error that takes the process down. Any of these conditions will cause the tunnel to time out or return an error on the in-flight request.
Designing federated schemas with nullability in mind limits the blast radius of these failures. When a subgraph tunnel fails to respond, the router can return null for that entity branch rather than propagating an error that nulls out the entire operation response. The Apollo documentation on nullability in federated graphs recommends evaluating referenced entity nullability case-by-case against internal SLAs—there is no single universal answer, but erring toward nullable at cross-service joins gives the router more flexibility for graceful degradation.
The @semanticNonNull directive, introduced in the nullability specification and now supported experimentally by Apollo Kotlin and several server-side frameworks, offers a more expressive alternative. Fields annotated with @semanticNonNull signal that null only appears in the presence of an entry in the errors array, not as a valid business value. This distinction allows clients to generate non-nullable properties in typed code while still handling the failure case that a tunneled subgraph going offline would produce.
Router support for @defer is also relevant here. When a local subgraph tunnel introduces latency—perhaps because the developer is stepping through code in a debugger—the client can use @defer to receive the non-deferred parts of the response immediately and wait for the local result asynchronously.
Distributed Tracing Across the Tunnel
Debugging complex query plan execution becomes significantly harder without end-to-end trace visibility. When a query fans out to both cloud-hosted subgraphs and a local tunneled subgraph, the trace should span all of them.
Apollo Router follows the W3C Trace Context specification for trace ID generation and propagation. The traceparent and tracestate headers are automatically included in subgraph requests. If the local subgraph is instrumented with OpenTelemetry—using @opentelemetry/instrumentation-graphql and an OTLP exporter pointing at a local or shared collector—its spans will appear as children of the router’s subgraph span in the distributed trace.
Router telemetry is configured in router.yaml under the telemetry key. Enabling trace_context: true under propagation ensures W3C headers are forwarded:
telemetry:
exporters:
tracing:
propagation:
trace_context: true
otlp:
enabled: true
endpoint: "http://localhost:4317"
The trace waterfall—showing the router’s query planning span, parallel subgraph fetch spans, and the local subgraph’s resolver spans as children—is the single most valuable debugging tool for federated N+1 analysis in a tunneling workflow.
9. Operational Maturity: What to Standardise on Teams
For teams moving beyond individual developer experimentation toward a shared, team-wide tunnelling practice, several operational norms reduce friction and prevent incidents.
Tunnel lifecycle automation. Tunnel URLs change on each ngrok restart unless a reserved domain is configured (ngrok’s paid tiers support stable custom subdomains). Add tunnel URL registration to the dev startup script rather than making it a manual step.
Coprocessor vs. Rhai for routing override. Rhai scripts run directly in the router process and require no additional infrastructure. For most teams, the header-parsing pattern described above is a good fit for Rhai. Coprocessors are appropriate when the routing decision requires external state—for example, a lookup table mapping developer identifiers to their current tunnel URLs maintained in a shared store.
Staging router isolation. Apply the header-based routing pattern to the shared staging router, not global subgraph URL replacement. Global replacement is appropriate only for short-lived feature branches where the developer owns the entire staging environment.
Schema composition validation in CI. Even with a local tunnel in place, schema changes should run through composition validation before merge. Rover’s rover subgraph check or the Hive CLI’s hive schema:check will catch breaking changes against the registry’s current composition before the pull request lands.
Security review for tunnel scope. Define and document which subgraphs are permitted to be tunnelled. Subgraphs with write access to production-adjacent data should require elevated authentication at the tunnel layer and may warrant review before being surfaced publicly, even temporarily.
10. Conclusion
The promise of GraphQL federation was team autonomy—independent ownership, independent deployment, independent iteration. The physical constraints of developer hardware threatened to hollow that promise out by making realistic local testing impractical.
Subgraph tunnelling restores it. By routing a specific subgraph’s traffic through a secure tunnel to a developer’s laptop while leaving all other subgraphs running in the cloud, the pattern gives engineers full-fidelity, debuggable access to the live graph without carrying its weight locally.
The tooling has matured considerably. rover dev with subgraph mirroring, Apollo Router’s Rhai scripting, and Hive’s branched schema registries together provide the building blocks for a production-grade workflow. The current stable foundation is Apollo Router v2.x with Federation v2.12 (the December 2025 LTS release), with Router v1.x having reached end of support in March 2026.
Header-based per-request routing is where the pattern becomes genuinely team-scale: multiple developers tunnelling different subgraphs simultaneously into the same shared router, each in complete isolation from the others. That is the workflow worth standardising on.
Changelog
| Change | Reason |
|---|---|
| Removed target keywords and hook line from article body | Draft metadata, not editorial content |
| Section 3 diagram description rewritten | Original conflated the Router’s internal staging DNS with the tunnel relay—clarified data flow |
Replaced “Apollo Rover CLI rover dev” description with accurate current behaviour |
rover dev starts a local router; it does not alter the shared cloud staging cluster. Added subgraph mirroring context from Rover changelog |
supergraph.yaml example updated to federation_version: =2.12.0 |
The current Federation LTS as of December 2025; original used an unspecified version |
| Added Apollo Router v2.10 / Federation v2.12 LTS release context (December 2025); v1.x EOS date (March 31 2026) | Sourced from Apollo blog, December 2025 |
| Replaced fabricated Rhai script | Original script logic was plausible but unsourced; rewritten to match documented subgraph_service hook signature and header access patterns from official Rhai API reference |
| Added coprocessor vs. Rhai performance figures (~100 µs / ~350 µs) | Sourced from Apollo’s published extensibility benchmark blog post |
| Noted coprocessors require GraphOS Enterprise plan | Sourced from Apollo Router documentation |
| Replaced WunderGraph section | WunderGraph pivoted to a new product offering; removed as the original description did not accurately reflect the company’s current positioning |
| GraphQL Hive section rewritten | Added Hive Gateway v2 and Hive Router (Rust) details, March 2025 access token migration, and CDN/Cloudflare delivery mechanism from official Hive docs |
Added @semanticNonNull directive discussion in Section 8 |
Relevant to failure handling for tunneled subgraphs; directive confirmed as experimentally supported by Apollo Kotlin 4 (August 2024 blog post) and other frameworks |
OpenTelemetry tracing section expanded with router.yaml config snippet |
Based on official Apollo Router telemetry documentation; traceparent/tracestate W3C propagation confirmed from Router docs |
| Section 9 (Operational Maturity) added | Not present in original draft; addresses team-scale concerns around tunnel lifecycle, staging isolation, and CI schema validation |
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.