Skip to content

fix compose include:, project name, volumes, dry-run, build tags and image references - #77

Merged
us merged 1 commit into
mainfrom
fix/compose-image-issue-sweep
Aug 9, 2026
Merged

fix compose include:, project name, volumes, dry-run, build tags and image references#77
us merged 1 commit into
mainfrom
fix/compose-image-issue-sweep

Conversation

@us

@us us commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Fixes the eight compose and image defects reported in #69-#76. Every report was
verified against the source first, and every fix was then reproduced end to end with a
binary built from this branch against the real container backend.

What changed

#69include: was silently dropped. ComposeFile.parse only read
services/networks/volumes, so a project whose services all arrive through
include: parsed to an empty model and up exited 0 having created nothing.
Includes now resolve recursively: short and long form, path as a string or a list,
per-entry project_directory and env_file, a cycle guard plus a depth cap, and
parent definitions overriding included ones. An included file's relative bind-mount
sources and build context anchor to that entry's project directory, and its
top-level name: no longer leaks into the including project. A malformed include:
now errors instead of parsing to nothing.

#70 — unknown service names exited 0. up/create/build/pull/push/stop/
start/rm/kill/restart/pause/unpause/exec/attach/logs/ps/top/port
now report no such service: <name> and exit 1. down is unchanged — it takes no
service arguments, in mocker or upstream.

#71COMPOSE_PROJECT_NAME ignored, config printed name: default. The project
name now resolves as -pCOMPOSE_PROJECT_NAME in the environment → in .env
top-level name: → directory basename, normalized to Compose's character set, and
config prints the resolved value.

#72down --volumes never removed anything. The flag was parsed and never read.
down -v now removes the project's named volumes, reports them in the progress output,
and leaves external: true volumes alone. An explicit name: on a volume is honored
for both creation and removal.

#73compose build tagged images with the bare service name. It computed its own
<service>:latest instead of ComposeService.buildTag, so a Dockerfile with
FROM caddy:latest resolved to mocker's own previous build. Builds now tag
<project>-<service>:latest, matching the tag the runtime looks up.

#74--dry-run was declared 35 times and read zero times. It is now declared once
on the shared compose options and honored by every mutating subcommand, which reports
what it would do and returns before touching any manager, engine or directory. It is
also accepted in Docker's position (compose --dry-run up).

#75images showed Zero KB / in 0 sec for everything. SIZE and CREATED are now
read from each image's manifest and config. A genuinely unknown value renders as N/A
rather than a confident zero.

#76rmi deleted the wrong image. References were normalized before the store
lookup, so rmi caddy:latest deleted the pulled docker.io/library/caddy:latest while
leaving the local build, and mocker-built images could not be removed at all. Reference
resolution now tries the literal reference first, then <name>:latest, then the
normalized form, and deletes exactly the reference that matched.

Fixes found while reviewing this change

  • stop/start/rm/kill/pause/unpause/logs/restart selected containers by
    substring, so stop web also stopped webhook; and project scoping by name prefix let
    project app reach app-prod's containers — restart stops and removes what it
    selects. Selection is now by the project/service labels compose has always written.
  • VolumeManager now rejects names that would escape the volumes directory: a compose
    file can supply a volume name verbatim.
  • up's progress counter counted declared resources rather than work performed, so a
    re-run printed 1/2 with one line. It now matches down.
  • compose config printed Swift's own struct description for build: and a Swift tuple
    for each environment entry, and did not quote values containing # or :. Its output
    now parses back as YAML.

Upgrade notes

  • Project names may change. If you relied on COMPOSE_PROJECT_NAME or a top-level
    name: — which mocker previously ignored — your project name changes to the value you
    actually set, and containers/volumes/networks created under the old name are no longer
    matched. Bring the project down before upgrading, or remove the old resources by hand.
  • compose build tags change from <service>:latest to <project>-<service>:latest.
    Old bare-tagged images stay on disk until removed; up rebuilds under the new tag.
  • MockerKit API: ImageInfo.size and ImageInfo.created are now optional. The Docker
    Engine API surface is unchanged — unknown values map to 0 at that boundary only.
  • Compose scoping is now label-only. Every container mocker's compose creates has
    carried com.mocker.compose.project/.service since the first release, so this is a
    no-op for them, but a container merely named like one (created by hand through the
    container CLI) is no longer picked up by compose subcommands. That is deliberate:
    by name alone, project app + service prod-web is indistinguishable from project
    app-prod + service web, and down/restart remove what they select.

Verification

swift build clean, swift test 406 tests / 32 suites green (345 before). Every issue
was also reproduced end to end against the real backend on this branch: include-only
project parsed, unknown service exits 1, .env project name applied, down -v removing
a live container's volume while leaving an external one, compose build producing
build-alpine:latest and the container starting from that same tag, build --dry-run
leaving no image, images showing 4.1 MB / 3mo ago, and rmi removing a
bare-tagged mocker build.

Not covered

  • Zh-CN docs (README.zh-CN.md, docs/zh-CN/) still describe the old project-name
    behavior and lack the include: section.
  • rmi still does not accept image IDs or digest prefixes (mocker images -q | xargs mocker rmi), which is a separate gap from the reference resolution fixed here.
  • Two other flags remain parsed-and-ignored in the same shape as compose build --dry-run performs a real build and leaves a tagged image #74: compose rm -v
    and compose down --rmi.
  • The effective project name is not exposed to ${COMPOSE_PROJECT_NAME} interpolation
    when it comes from -p or the file's name:, since interpolation runs before the file
    is parsed.
  • ComposeNetwork still has no external: field, so networks do not get the ownership
    handling volumes just gained. up/down only ever touch <project>-<network>, so an
    external network is never removed — it simply cannot be joined yet.

ref #69, ref #70, ref #71, ref #72, ref #73, ref #74, ref #75, ref #76

- include: resolved recursively — short and long form, per-entry
  project_directory and env_file, cycle guard and depth cap, parent
  definitions winning; an included file's relative bind mounts and build
  context anchor to its own directory and its name: stays local (#69)
- unknown service names error with "no such service" and exit 1 instead of
  reporting success on an empty selection (#70)
- project name resolves as -p, COMPOSE_PROJECT_NAME (environment, then .env),
  top-level name:, then the directory, and config prints what it resolved (#71)
- down --volumes removes the project's named volumes and leaves external
  ones alone; up and down now agree on the volume set they own (#72)
- compose build tags <project>-<service>:latest, the same tag the runtime
  starts the service from, instead of shadowing the base image (#73)
- --dry-run is declared once and honored by every mutating subcommand,
  returning before any manager, engine or directory is touched (#74)
- images reads real SIZE and CREATED from each image's manifest and config,
  rendering N/A rather than a confident zero when unavailable (#75)
- image references resolve literally before normalizing, so rmi removes the
  image that was named and can remove locally built ones (#76)

Found while reviewing the above: service selection matched by substring, so
stop web also stopped webhook, and project scoping by name prefix let one
project reach another's containers; both are now label-based. Volume names
that would escape the volumes directory are rejected, and compose config
renders build:, environment and quoted scalars as valid YAML.

BREAKING CHANGE: projects that set COMPOSE_PROJECT_NAME or a top-level name:
resolve to that name now, and names are normalized to [a-z0-9_-], so existing
containers, volumes and networks created under the previous name are no longer
matched. compose build tags <project>-<service>:latest instead of the bare
service name. MockerKit: ImageInfo.size and ImageInfo.created are optional,
and ComposeEvent has a new volumeRemoved case.
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.

1 participant