feat(engine): option to drop compose host port publishing - #137
Merged
Merged
Conversation
EngineOptions.DisableHostPortPublishing drops the host side of compose `ports:` entries. Services still start and nothing else about them changes; each dropped entry raises an engine.warn event (compose_host_port_publish_skipped) naming the service and where it is reachable instead. An embedder whose daemon "host" is a namespace shared with other workloads — the engine running inside a Kubernetes pod next to sidecars — cannot let a project bind there: a service publishing 8080 loses the race against a sidecar already listening and the boot fails on a raw `bind: address already in use`. Nothing in that shape consumes the publish anyway, since service-to-service traffic goes over the compose network by service name and an embedder forwarding a port out of the namespace dials the container on that network — which is what makes dropping it safe, and a silent default acceptable. It still warns, and the message says where the service is reachable rather than only that something was ignored. The entries are removed from the in-memory project via a new compose.ApplyDropHostPorts, alongside the existing Apply*Override helpers, rather than inside the orchestrator's portsOf: ConfigHash reads the project's ServiceConfig, so a container created before the option was turned on drifts and is recreated instead of being reused with its publishes intact. Native backend only. The shellout path hands `ports:` to `docker compose`, which publishes them regardless, and stripping them there would mean re-implementing merge/extends/override resolution against the YAML — so a compose-source Up under ComposeBackendShellout is refused rather than run with the option silently ignored. `network_mode: host` has the same collision but needs a refusal, not a drop: moving the service to a bridge network would change reachability. Filed as #136. Closes #135 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Member
Author
|
@dap-code-review-by-crunchloop review The check for this head reported "Review did not complete" — nothing was published for |
|
On it. A review of the current head is on its way. |
bilby91
marked this pull request as draft
September 16, 2026 17:05
bilby91
marked this pull request as ready for review
September 16, 2026 17:05
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #135.
EngineOptions.DisableHostPortPublishingdrops the host side of composeports:entries. Services still start and nothing else about them changes;each dropped entry raises an
engine.warnevent(
compose_host_port_publish_skipped) naming the service and where it isreachable, so the user can delete the entry rather than wonder why it did
nothing.
Design decisions
Naming:
DisableHostPortPublishing, notSharedHostNamespace. The issueleft this open, with the why-name reading better if
network_mode: hostlaterjoins the same option. I drafted it that way first and it misread immediately —
an option called "shared host namespace" sounds like it puts the engine on
the host's namespace, which is the opposite of what it does. The what-name says
what changes; the why lives in the doc comment.
Dropped from the in-memory project, not inside the orchestrator's
portsOf. The issue suggestedportsOfreturns nil.ConfigHashhashes theproject's
ServiceConfig, so a drop there is invisible to the recreate check:a container created before the option was turned on would keep its publishes
and be reused. Removing the entries from the project instead makes the hash
move, which is the correct behaviour and a deliberate recreate trigger (R3) —
turning the option on replaces the containers of services that declare
ports:. This also reuses the existing seam:compose.ApplyDropHostPortssits next to
ApplyBuildOverride/ApplyRunOverride, whose whole job ismutating the project before the orchestrator reads it, and keeps the
orchestrator free of a new option and a new warning channel (it has no event
surface today; the engine already has one).
Entries are removed whole, including ones with no
published.ports: ["8080"]binds an ephemeral host port — same namespace, same problem.Shellout is refused, not silently ignored (R2). That path hands the user's
compose files to
docker compose, which publishesports:regardless, andstripping them there would mean re-implementing merge /
extends/ overrideresolution against the YAML. A compose-source
UpunderComposeBackendShelloutwith the option set returns an error naming thebackend that honours it. The failure the option exists to prevent is a
boot-time bind collision, which is worse to debug than a refusal at the top of
the call.
network_mode: hostis not covered. Same collision, but a drop is notavailable: silently moving the service to a bridge network changes
reachability. It wants a plan-time refusal, which needs the option to reach
Plan.Validate. Filed as #136 with the shape left open.Tests
compose/apply_drop_host_ports_test.go— every entry on every service goes,in sorted service order (warning order must not depend on map iteration);
published-less entries are dropped and rendered without a host side;nothing but
ports:moves; and theConfigHashpin for the paragraph above.up_compose_host_ports_test.go— the consumer check (R1): the option reachesRunSpec.Portsat the backend boundary, not just the project; the warningfires and names the service, the entry, and the address to use instead; the
publish still reaches the backend by default; shellout refuses. Reverting the
call in
upComposeNativefails the first of these.test/integration/compose_host_port_publishing_test.go— a real daemon bootsa project whose primary publishes a port with the option on, and the sidecar
is still reachable by service name over the compose network. That is the half
a fake cannot make, and it is the claim the whole design rests on. What the
host binding does or does not do is asserted at the
RunSpecboundary above;RunSpec.Ports→ dockerPortBindingstranslation is already covered inruntime/docker.make lint testgreen. Integration tests were not run locally — overlayfsis unusable in this devcontainer, so the pre-existing suite fails there the same
way (
mount source: "overlay" … invalid argument); they run in CI.🤖 Generated with Claude Code