Skip to content

feat(provider): publish-endpoint deploys a network relay for provider services - #14193

Draft
ndeloof wants to merge 1 commit into
docker:mainfrom
ndeloof:provider-relay
Draft

feat(provider): publish-endpoint deploys a network relay for provider services#14193
ndeloof wants to merge 1 commit into
docker:mainfrom
ndeloof:provider-relay

Conversation

@ndeloof

@ndeloof ndeloof commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

A provider's resource lives outside the compose network: consumers could only reach it through injected variables carrying a host-published address. This adds the transparent path:

  • publish-endpoint provider message: {"type": "publish-endpoint", "message": "80=host.docker.internal:49152"} — container port consumers know on the left, real location on the right.
  • Relay container: when endpoints are published, compose deploys docker/compose-relay (new relay/ directory: static Go TCP forwarder, FROM scratch, bake target relay-image, COMPOSE_RELAY_IMAGE override for internal registries) in place of the service — canonical <project>-<service>-1 name, service alias on the networks of the depending services. Consumers use http://<service>:<port>, no injected variables involved.
  • First-class project container: standard compose labels incl. config-hash, so label-driven commands run without the compose file (ps, logs, stop, down) keep seeing the service — plus com.docker.compose.relay (value: identity hash of image+routes) declaring its role:
    • up idempotent: relay kept when routes unchanged, recreated otherwise (reconciler already leaves provider services' containers alone);
    • exec/cp refuse a relay (no service process to act on);
    • the attached-up monitor excludes relays from the containers whose termination ends the command — they are long-lived infrastructure.

This benefits every provider: the example provider demonstrates the flow (detached HTTP helper + publish-endpoint), covered by an e2e scenario asserting http://db works from a consumer and exec db is refused, plus unit tests (message parsing, route identity, network selection, relay guard, image override).

Standalone — no dependency on #14175 (the two protocol additions are orthogonal).

Publishing note for maintainers: docker/compose-relay is a new image to publish on Docker Hub under the docker/ organization (bake target included; release wiring to be added to CI).

🤖 Generated with Claude Code

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

This PR introduces a well-structured relay mechanism for provider services — the core logic in relay.go, plugins.go, and relay/main.go is sound. One low-severity issue was found in the example/demo code only.

Comment thread docs/examples/provider.go Outdated
@ndeloof
ndeloof force-pushed the provider-relay branch 2 times, most recently from c0c356c to fee8789 Compare September 6, 2026 16:48
@codecov

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 58.08081% with 83 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/compose/relay.go 54.54% 66 Missing and 9 partials ⚠️
pkg/compose/cp.go 20.00% 2 Missing and 2 partials ⚠️
pkg/compose/plugins.go 80.95% 2 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@ndeloof
ndeloof force-pushed the provider-relay branch 3 times, most recently from fea2480 to 893cb95 Compare September 7, 2026 06:53
@ndeloof

ndeloof commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

👋 Heads up: The /review command is deprecated. Please re-request a review from docker-agent in the PR sidebar instead.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

Two findings in the new relay code introduced by this PR.

Comment thread pkg/compose/relay.go Outdated
Comment thread relay/main.go
@ndeloof

ndeloof commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

👋 Heads up: The /review command is deprecated. Please re-request a review from docker-agent in the PR sidebar instead.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

Two medium-severity correctness/reliability issues and two low-severity issues were found in the new relay code.

Findings summary:

  • relay/main.go: wg.Wait() does not drain active forward() connections on shutdown (medium, 🟢 strong 100/100)
  • pkg/compose/relay.go: paused relay with matching identity is force-removed instead of unpaused (medium, 🟢 strong 97/100)
  • pkg/compose/relay.go: missing existence check before project.Networks[key].Name (low, 🟡 moderate 57/100)
  • docs/examples/provider.go: bufio.ReadString has no timeout when reading serve-demo address (low, 🟢 strong 100/100)

Comment thread relay/main.go
Comment thread pkg/compose/relay.go Outdated
Comment thread pkg/compose/relay.go
Comment thread docs/examples/provider.go
@ndeloof

ndeloof commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

👋 Heads up: The /review command is deprecated. Please re-request a review from docker-agent in the PR sidebar instead.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🔴 CRITICAL

1 critical finding (high-severity CONFIRMED bug) and 1 minor finding (low-severity CONFIRMED).


Summary: The PR introduces a solid relay mechanism for provider services. The main correctness issue is an orphan container left behind when network attachment partially fails; the Dockerfile has a dead ARG that should be wired up.

Comment thread pkg/compose/relay.go
Comment thread relay/Dockerfile Outdated
@ndeloof

ndeloof commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

👋 Heads up: The /review command is deprecated. Please re-request a review from docker-agent in the PR sidebar instead.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

Two medium-confidence findings in the new relay code introduced by this PR.

Comment thread relay/main.go Outdated
Comment thread pkg/compose/relay.go Outdated
@ndeloof

ndeloof commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

👋 Heads up: The /review command is deprecated. Please re-request a review from docker-agent in the PR sidebar instead.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

One medium-severity finding: the relay guard introduced by this PR is incomplete in pkg/compose/cp.go — the default branch (common cp usage without --index) skips the checkRelayTarget check that was correctly added to the options.Index > 0 branch.

Comment thread pkg/compose/cp.go
@ndeloof

ndeloof commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

👋 Heads up: The /review command is deprecated. Please re-request a review from docker-agent in the PR sidebar instead.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 No issues found — LGTM! View logs.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

One confirmed issue found in the new relay deployment code.

Comment thread pkg/compose/plugins.go
}
}
if command == "up" && len(variables.endpoints) > 0 {
if err := s.ensureServiceRelay(ctx, project, service, variables.endpoints); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] ensureServiceRelay called while holding global mutex — serializes concurrent provider deployments

runPlugin acquires mux (line 96) to protect the shared project.Services env-var writes, then—under the same defer mux.Unlock()—calls ensureServiceRelay (line 114) which performs multiple Docker API operations:

  • findRelayContainerContainerList (network I/O)
  • ContainerCreate (network I/O)
  • pullRelayImageImagePull + stream drain (potentially minutes on first pull)
  • ContainerStart (network I/O)
  • waitRelayRemoved → 250 ms polling loop for up to 30 seconds

runPlugin is dispatched concurrently for all provider services by the plan executor's errgroup (see executor.go line 107). With the mutex held across these slow Docker API calls, every concurrent provider is forced to wait for the slowest one—including its image pull. Before this PR, the mutex only guarded a tight in-memory loop.

Fix: release mux before calling ensureServiceRelay. The relay deployment reads and writes only Docker API state; it does not need the mutex that protects project.Services.

Suggested change
if err := s.ensureServiceRelay(ctx, project, service, variables.endpoints); err != nil {
if command == "up" && len(variables.endpoints) > 0 {
mux.Unlock()
err := s.ensureServiceRelay(ctx, project, service, variables.endpoints)
mux.Lock()
if err != nil {
return err
}
}
return nil

Note: the defer mux.Unlock() must be replaced with an explicit Unlock around the env-var loop if taking this approach—or restructure so ensureServiceRelay is called after mux is released naturally.

Confidence Score
🟢 strong 97/100

… services

A provider's resource lives outside the compose network: consumers could
only reach it through injected variables carrying a host-published
address — nothing like the compose-native experience of addressing a
service by name at its well-known port.

A provider may now publish where each endpoint of its resource actually
listens:

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

The endpoint is announced as seen from the provider's host: the relay —
the component that knows it runs inside a container — rewrites loopback
or unspecified upstream hosts to host.docker.internal (resolved through
its injected host-gateway extra_host); routable addresses pass through.

When at least one endpoint is published, compose deploys a relay
container in place of the service: a minimal TCP forwarder (new relay/
directory, published as docker/compose-relay, overridable with
COMPOSE_RELAY_IMAGE for internal registries) joining the networks of the
services that depend on the provider service, aliased with the service
name. Consumers then use http://<service>:<port> as if the service were
a regular container.

The relay is a first-class project container — canonical name, standard
compose labels including config-hash (label-driven commands run without
the compose file keep seeing the service: ps, logs, stop, down) — plus
the com.docker.compose.relay label declaring its role:

- the reconciler already leaves provider services' containers alone, and
  the relay's identity hash (image + routes) makes up idempotent: kept
  when routes are unchanged, recreated otherwise;
- process-level commands (exec, cp) refuse a relay — there is no service
  process in it to act on;
- the up monitor excludes relays from the containers whose termination
  ends an attached up: they are long-lived infrastructure and would
  otherwise keep 'up' waiting forever.

The example provider demonstrates the flow behind PROVIDER_DEMO_ENDPOINT
(a detached helper serving a fixed HTTP response), backed by an e2e
scenario asserting the compose-native address works and exec is refused.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants