Decision record

AD-0010 The router as a child process from the copied binary

Status: accepted. Date: 2026-08-27.

Context

The demo ships as one image started by one docker run, and the Cosmo router has to be inside it. The vendor publishes the router as an image, ghcr.io/wundergraph/cosmo/router, built with CGO_ENABLED=0 onto distroless static for linux/amd64 and linux/arm64, with the binary at /router and about 40 MB compressed. It is also a Go module, and the module can in principle be embedded: core.NewRouter, WithStaticExecutionConfig and Router.Start are exported, and the documented custom-binary pattern is routercmd.Main() with blank-imported modules.

What the module does not offer is a stable footing. Its release tags are of the form [email protected], which Go tooling cannot resolve, so only commit pseudo-versions exist. The official router-examples README requires a block of OpenTelemetry replace directives and says compatibility is not guaranteed without them. The module API broke at 0.188.0 and 0.278.0, a new module system is announced for the next major release, WithModulesConfig takes map[string]interface{}, and no docs page describes direct construction through core.NewRouter. No Go API stability promise is published anywhere. Embedding would also pull graphql-go-tools, the process extension library and the NATS, Kafka and Redis clients into this module’s dependency graph, and the empty-interface signature would sit in hand-written code, which the repository’s own rule forbids outside a reviewed exception.

Copying the binary has its own conditions. COPY --from accepts an image reference, the router is statically linked, and copying /router out of the pinned image is legitimate under Apache 2.0 provided the LICENSE travels with it. The router binary has no probe subcommand, so a container built on it alone could carry no HEALTHCHECK. Whether COPY --from resolves the target platform’s variant in a multi-platform build was unverified when this record was written. A Go supervisor that is PID 1 and the router’s direct parent reaps it through os/exec without tini. Reaping of grandchildren is only likely, and the two known sources of grandchildren are the usage tracker and the router’s extension mechanism.

Decision

We will run the Cosmo router as a child process of the supervisor, from the binary copied out of ghcr.io/wundergraph/cosmo/router:0.343.1 with a COPY --from stage, driven by environment and the committed execution configuration alone. The image copies /router and fetches the vendor’s LICENSE at the pinned tag with a pinned checksum to sit beside it, the supervisor starts the child with LISTEN_ADDR=127.0.0.1:3002, EXECUTION_CONFIG_FILE_PATH=/app/config.json, PLAYGROUND_PATH=/playground and the four telemetry variables of SR-03, and waits for /health/ready before opening the published port.

Alternatives considered

Embedding the router as a Go library through core.NewRouter and WithStaticExecutionConfig, with the subgraphs as in-process servers. One static binary, one process, the smallest image, and programmatic control of playground, telemetry and listen address. It lost on the module’s footing: pseudo-versions only, the replace block, two API breaks, an announced rewrite, an undocumented construction path and no stability promise, plus an empty-interface signature and a dependency graph that would carry message brokers the demo never uses.

The vendor’s custom-binary form, routercmd.Main() with custom modules, which is the supported shape of embedding. It takes over main, reads its configuration from file and environment as the stock binary does, and offers the demo nothing the copied binary does not, while carrying every dependency and version problem of the library route.

The router’s extension mechanism under Cosmo Connect, or standalone gRPC services, as the way to co-locate the subgraphs with the router. Officially supported, and the router would supervise the extensions itself. Neither supports subscriptions, and the two apps depend on live version events (AD-0014), so this route would have replaced live push with polling.

Consequences

The router is a vendor artefact inside the image, pinned by tag and bumped by rebuilding. Nothing in the Go module depends on it, so the router’s Go API can change without touching this repository, and a router upgrade is a change to the Dockerfile and to the wgc version composed with it, which must move together. The licence obligation is explicit: the image carries the router’s LICENSE next to /router (SR-07), and NOTICE names the router with its version (SR-08).

The cost is a second process to start, watch and stop. The supervisor is the parent of a child it cannot inspect from the inside, so readiness is read from /health/ready and the healthcheck subcommand of AD-0011 probes on the router’s behalf. What /health/ready reports before the subgraphs answer is a spike, and the supervisor’s ordering, subgraphs first, router last, published port after readiness, is chosen so as not to depend on the answer.

The router sets the floor of the image size, around 40 MB compressed of SR-06’s 80 MB budget. SR-05 rests on another spike: if COPY --from does not resolve the target platform, the arm64 image would carry an amd64 router and fail on Apple silicon, which is why the arm64 layer is inspected before the first tag. A third settles whether the copied binary starts from environment alone in a distroless image with no YAML file present. All three are reported in Five spikes before the first line.

Grandchild reaping stays likely rather than verified. The design removes the two known sources, since the usage tracker is disabled by SR-03 and no router extension is loaded, and no tini is added.

Requirements affected

SR-03, SR-05, SR-06, SR-07

Sources

The Cosmo router image and its LICENSE at 0.343.1, the router module’s release tags and the replace block its examples repository requires, and the vendor’s pages on running the router from environment and a static execution configuration. Five views and twenty-six decisions for the runtime and deployment views, and What the research overturned for the embedding claim as it was checked.