make compose networks actually work: external:, project isolation, down --rmi - #80
Merged
Merged
Conversation
…red flags Compose networks did nothing. NetworkManager persisted them to a JSON directory of mocker's own that the runtime never saw, and ContainerConfig.network was set and then never emitted, so every container landed on the runtime's global network no matter what the file declared — and `mocker run --network` was ignored too. - NetworkManager now goes through the real `container network` CLI, so what it reports and removes is what the runtime actually has; connect/disconnect say they are unsupported instead of writing a record that changed nothing - buildRunArguments emits --network, which is what attaches a container - services that name no network get a project-scoped `<project>-default`, so unrelated projects no longer share one network - networks support `external:` and `name:`: up verifies an external network exists and fails naming it, never creates it, and down never removes it - a service's `networks:` mapping form is parsed; it previously read as no networks at all - a container records the network it was created on, so changing a service's network recreates it instead of leaving it on the old one - `down --rmi all|local` removes the services' images, `local` limited to the ones compose built; an invalid value is rejected rather than ignored - `rm --volumes` errors: nothing tracks anonymous volumes, so honoring it would report a cleanup that never happened - `--timeout` reaches `container stop -t` instead of being parsed and dropped - `network prune` no longer removes the runtime's built-in network, and counts only what it actually removed - down --rmi and compose stop warn on stderr rather than reporting success for something that failed BREAKING CHANGE: containers now really join the networks a compose file declares, so services that relied on every container sharing the runtime's global network can no longer reach services in other projects. Networks are no longer stored under ~/.mocker/networks; `network connect`/`disconnect` report that the runtime does not support them. MockerKit: NetworkInfo.created is optional, NetworkManager's methods are async and throwing, and ObservedContainer gained a network field.
RealProcessRunner waited for a child with `waitUntilExit()`, which can wedge a thread Swift concurrency still needs. A command that shells out once got away with it; now that networks go through the CLI, `compose down` makes several calls in a row and hung indefinitely — 150s and counting, against 2s once exit is observed through terminationHandler instead. Pipes are drained before the process starts so a full buffer cannot block the child either. Also skip network removal for a project that is already down: it retried three times and then told the user to remove a network that was not there.
… error `rm --volumes` refused to run at all, so a flag that merely has nothing to do also stopped the containers from being removed. It now warns and removes them, matching how the codebase treats a flag the runtime cannot honor. Network creation failures no longer lose the runtime's own message: a rejected name or an unavailable backend was being reported as a generic "failed to create network".
This was referenced Aug 9, 2026
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.
Follow-up to #77, taking the three items its "Not covered" section listed. The first one
turned out to sit on top of a much larger gap.
Compose networks did nothing at all
networks:was decorative.NetworkManagerpersisted networks to a JSON directory ofmocker's own that the runtime never saw, and
ContainerConfig.networkwas set and thennever emitted —
buildRunArgumentshad no--network. So every container, compose ornot, landed on the runtime's
defaultnetwork no matter what the file said, andmocker run --networkwas ignored too.NetworkManagernow goes through the realcontainer networkCLI (create / ls /delete / inspect) via the injectable
ProcessRunning, so what it reports is what theruntime actually has.
buildRunArgumentsemits--network, which is what attaches the container.connect/disconnectnow say they are unsupported instead of writing a record thatchanged nothing. README no longer advertises them.
Verified end to end: a service on a declared network lands on it (
192.168.66.2onfinal-backend, confirmed viacontainer inspect), andmocker run --networkdoes too.external:for networks (the original ask)Mirrors the volume design from #77:
ComposeNetworkgainedexternalandcustomNamewith
runtimeName(projectName:), plus purenetworksToCreate/networksToRemove.upverifies an external network exists before starting anything and fails naming it,never creates it, and
downnever removes it. An explicitname:is used verbatim.A service's
networks:mapping form (networks: {backend: {aliases: [db]}}) is nowparsed — it previously read as no networks at all, which silently left the container on
the default network. A service listing several networks joins the first and says so on
stderr: attaching a container to two networks fails inside the guest on this runtime.
compose down --rmi all|localImplemented, with a pure
imagesToRemove:localremoves only images compose built(services with no explicit
image:),allincludes pulled ones. Removal is best-effortper image so a shared or missing image cannot abort the teardown, and each removal is
reported. An invalid value is rejected up front instead of being accepted and ignored.
compose rm --volumesWarns and carries on. Upstream removes the anonymous volumes attached to the removed
containers; mocker never creates one — an anonymous mount is dropped before it reaches the
runtime — so there is nothing to remove. Reporting a cleanup that did not happen is the
pattern #72/#74 were about, but failing the whole command over a flag with nothing to do
would stop
rmfrom removing the containers the user actually asked about.Also fixed here
networks:now get a project-scoped<project>-defaultinstead oflanding on the runtime's global network, where unrelated projects could reach each other.
recreates it — and a container from a version that recorded none is recreated too, which
is what moves an upgraded project onto its own network.
started against a network nothing creates.
network pruneno longer removes the runtime's built-in network, and counts only whatit actually removed.
Fixes found while reviewing this change
compose --timeoutwas parsed and dropped:ContainerEngine.stopnever took one, soevery teardown used the runtime's own default. It is now passed as
container stop -t,which also makes
downpredictable instead of appearing to hang.upno longer swallows a failed network creation. The runtime rejects names it does notlike, and a service cannot join a network that was never created.
downretries network removal briefly (the runtime can still consider a just-removedcontainer attached) and warns on stderr if it still could not remove it.
network createsays so when it is handed adriverorgatewaythe runtime cannotapply, rather than reporting success for a network that does not match the request.
A deadlock this work surfaced
RealProcessRunnerwaited onwaitUntilExit(), which can wedge a thread Swiftconcurrency still needs. One shell-out per command got away with it; once networks went
through the CLI,
compose downmakes several calls in a row and hung indefinitely(measured: 150s and still going, against 2s after the fix). Exit is now observed through
terminationHandler, matching whatContainerEnginealready did, and the pipes aredrained before the process starts.
downalso skips removal work entirely for a projectthat is already down, instead of retrying and then warning about a network that is gone.
Verification
swift buildclean,swift test434 tests / 35 suites green (417 before). Exercisedagainst the real backend: external network missing →
upfails naming it and createsnothing; present → joined, not created, and left alone by
down; a project-owned networkis created, joined, and removed;
down --rmi localremoved the built image and kept thepulled base;
--rmi allremoved both;--rmi watandrm -vare rejected;mocker network create/ls/inspect/rmall operate on real networks. Teardown timingsafter the deadlock fix: 2-3s across every fixture, against 5s on v0.8.0 for a project
that was not really networked at all.
Not covered
reproducible here. The warning names which one is used.
network connect/disconnectneed runtime support that does not exist yet.ref #77