Description
The provider extension lets a provider announce service endpoints with publish-endpoint messages; Compose then deploys a relay container carrying the service's name on the project networks, so dependent services reach the provider's resource at the compose-native address (http://<service>:<port>).
The relay rewrites host-relative upstreams (localhost, loopback or unspecified addresses) to host.docker.internal (pkg/compose/relay.go, relayUpstream), provisioned through ExtraHosts: host.docker.internal:host-gateway on engines that do not resolve it natively.
This transport is not equivalent across platforms when the provider's endpoint is bound to the host's loopback interface:
- On Docker Desktop (all OSes),
host.docker.internal traffic is proxied by the VM through the host's own loopback, so a 127.0.0.1-bound endpoint is reachable from the relay.
- On a standalone Linux engine,
host-gateway resolves to the bridge gateway address. A connection from the relay container arrives on the bridge interface with that destination address, which a listener bound only to 127.0.0.1 (or ::1) cannot accept. The endpoint publishes successfully, the relay deploys, and dependent services then get connection failures at the compose-native address.
The consequence for provider authors: a published endpoint must be bound to an address reachable from the bridge (in practice the wildcard) to work on standalone Linux engines, which exposes the port on every host interface — or bound to loopback to stay off the LAN, which limits the provider to Docker Desktop. The bundled example provider documents and works around this by binding 0.0.0.0 (docs/examples/provider.go):
// All interfaces, not loopback: on a plain Linux engine host-gateway
// is the bridge IP, which cannot reach a host loopback bind.
server := exec.Command(os.Args[0], "serve-demo", "0.0.0.0:0")
Reproduction
On a standalone Linux engine (not Docker Desktop), run a provider that binds its endpoint to 127.0.0.1:<port> and publishes it, with a dependent service consuming http://<provider-service>:<port>:
docker compose up succeeds — the endpoint is published and the relay starts.
- The dependent service's requests to the compose-native address fail to connect.
The e2e suite does not cover this case: TestProviderPublishEndpoint (pkg/e2e/providers_test.go) exercises the full relay path on Linux CI, but with the example provider's wildcard bind. A variant with a loopback-bound endpoint fails on a standalone Linux engine today.
Description
The provider extension lets a provider announce service endpoints with
publish-endpointmessages; Compose then deploys a relay container carrying the service's name on the project networks, so dependent services reach the provider's resource at the compose-native address (http://<service>:<port>).The relay rewrites host-relative upstreams (
localhost, loopback or unspecified addresses) tohost.docker.internal(pkg/compose/relay.go,relayUpstream), provisioned throughExtraHosts: host.docker.internal:host-gatewayon engines that do not resolve it natively.This transport is not equivalent across platforms when the provider's endpoint is bound to the host's loopback interface:
host.docker.internaltraffic is proxied by the VM through the host's own loopback, so a127.0.0.1-bound endpoint is reachable from the relay.host-gatewayresolves to the bridge gateway address. A connection from the relay container arrives on the bridge interface with that destination address, which a listener bound only to127.0.0.1(or::1) cannot accept. The endpoint publishes successfully, the relay deploys, and dependent services then get connection failures at the compose-native address.The consequence for provider authors: a published endpoint must be bound to an address reachable from the bridge (in practice the wildcard) to work on standalone Linux engines, which exposes the port on every host interface — or bound to loopback to stay off the LAN, which limits the provider to Docker Desktop. The bundled example provider documents and works around this by binding
0.0.0.0(docs/examples/provider.go):Reproduction
On a standalone Linux engine (not Docker Desktop), run a provider that binds its endpoint to
127.0.0.1:<port>and publishes it, with a dependent service consuminghttp://<provider-service>:<port>:docker compose upsucceeds — the endpoint is published and the relay starts.The e2e suite does not cover this case:
TestProviderPublishEndpoint(pkg/e2e/providers_test.go) exercises the full relay path on Linux CI, but with the example provider's wildcard bind. A variant with a loopback-bound endpoint fails on a standalone Linux engine today.