compose/relay
Nicolas De Loof 7c2c393d74 ci: lint, vet and test relay/ as its own module
relay/ (github.com/docker/compose-relay) is a separate go.mod: root
`go vet`/`golangci-lint`/`go test` never covered it, and the local
pre-commit hook explicitly excludes any directory with its own go.mod
for the same reason. Only the image build (relay-image-cross) was ever
validated in CI -- the Go source itself had no quality gate.

Adds relay-lint and relay-test bake targets, mirroring the root lint/
test Dockerfile stages: a build-base downloading go.mod's (currently
empty) dependency set, a lint stage running the same pinned
golangci-lint version as the root, and a test stage running go vet
then go test. relay-lint joins the "validate" bake group; both are new
matrix entries in the CI validate job.

relay/ gets its own .golangci.yml (the lint stage's build context is
"./relay" only, so it can't see the root config) with the same rule
set as root, minus what doesn't apply to a dependency-free module
(the e2e build tag, testify/gotest.tools-specific depguard entries).

Turning lint on for the first time surfaced 8 real findings in
relay/main.go and main_test.go (unchecked Close() errors, %v instead
of %w on a wrapped error, a redundant embedded-field selector, and
log.Fatalf after a defer that would never run) -- fixed here so the
new job is green from the start, not merged red.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2026-09-17 09:23:54 +02:00
..
.golangci.yml ci: lint, vet and test relay/ as its own module 2026-09-17 09:23:54 +02:00
Dockerfile ci: lint, vet and test relay/ as its own module 2026-09-17 09:23:54 +02:00
go.mod feat(provider): publish-endpoint deploys a network relay for provider services 2026-09-16 17:03:23 +02:00
main.go ci: lint, vet and test relay/ as its own module 2026-09-17 09:23:54 +02:00
main_test.go ci: lint, vet and test relay/ as its own module 2026-09-17 09:23:54 +02:00
README.md feat(provider): publish-endpoint deploys a network relay for provider services 2026-09-16 17:03:23 +02:00

compose-relay

compose-relay is the network gateway Docker Compose deploys in place of a provider-managed service, so the other services of the project reach the provider's resource at the compose-native address — http://<service>:<port> — even though that resource lives outside the compose network.

Why it exists

A service can delegate its implementation to an external provider:

services:
  web:
    build: .
    depends_on:
      - database

  database:
    provider:
      type: awesomecloud

The provider's resource (a cloud database, a sandbox, a host process, ...) is not a container on the project network: web cannot resolve database, and the resource's ports are typically published somewhere on the host, at addresses and port numbers the application does not know. Until now consumers had to read injected environment variables to locate it.

When the provider declares where each endpoint actually listens, with one publish-endpoint message per port:

{ "type": "publish-endpoint", "message": "5432=localhost:49152" }

Compose deploys this relay in place of the service. web then connects to database:5432 exactly as if the service were a regular container; the relay forwards the connection to the real endpoint.

How it runs

Compose creates the relay container from the published docker/compose-relay image (override with COMPOSE_RELAY_IMAGE, e.g. for air-gapped setups or local development) with:

  • the service's canonical container name (<project>-<service>-1) and a network alias set to the service name, on the networks of every service that depends on the provider service;

  • the standard compose labels, so label-driven commands (ps, logs, stop, down) treat it as the service — plus the com.docker.compose.relay label identifying its role. Its value is a hash of the routes, letting up keep an up-to-date relay and recreate a stale one. Commands that act on a service's process (exec, cp) refuse relay containers;

  • the routes as environment:

    RELAY_ROUTES=5432=localhost:49152[,<port>=<host>:<port>...]
    

The binary listens on every declared container port and forwards each connection to its endpoint. TCP only, with half-close propagation so protocols relying on EOF work through the relay. It is intentionally minimal: a static Go binary on a scratch image, no configuration reload — Compose recreates the relay when the published endpoints change.

Building

$ docker buildx bake relay-image