mocker version: 0.7.2 (Homebrew) — macOS, Darwin 25.5.0, arm64
Summary
When a compose service has build: and no image:, mocker tags the built image with the service name alone rather than <project>-<service>. If the service's Dockerfile has a FROM referring to that same name — the very common case of a thin wrapper over an official image — the next build resolves FROM to mocker's own previous output instead of the upstream base. Builds then stack on top of each other silently, and the local tag shadows the official image.
Reproduced below: the second build of a FROM caddy:latest Dockerfile used the first build's manifest as its base.
Reproduction
caddy/Dockerfile:
FROM caddy:latest
COPY ./caddy/Caddyfile /etc/caddy/Caddyfile
EXPOSE 80 443
Compose service (no image: key):
services:
caddy:
build:
context: /abs/path/to/repo/caddy
dockerfile: Dockerfile
Build #1:
$ mocker compose -f flat.yml -p laradock --project-directory /abs/path/to/repo build caddy
...
#7 exporting manifest list sha256:e2fa1a8d5a731b594df9fa23e705a47eced3419232494bccf049d94aacad67d3
caddy:latest
Successfully built caddy
Resulting tag is caddy:latest, colliding with the official image already present:
$ mocker images | grep -i caddy
caddy latest sha256:e2fa1 # <- mocker's build
docker.io/library/caddy latest sha256:844f6 # <- official base
$ mocker images | grep -c laradock
0 # no project prefix anywhere
Build #2, --no-cache, same command:
#1 [resolver] fetching image...docker.io/library/caddy:latest
#5 resolve docker.io/library/caddy:latest@sha256:e2fa1a8d5a731b594df9fa23e705a47eced3419232494bccf049d94aacad67d3
#7 exporting manifest list sha256:4b9bab58f2bfea78e2ab8508a77370b555885ed8f231c9d311fc6a4c4e6f60a9
Line #5 is the defect: the base resolved to sha256:e2fa1a8d…, which is build #1's output, not the official sha256:844f60b6…. The COPY was applied on top of an image that already had it. Every rebuild adds another layer on the previous rebuild.
Image store afterwards — two distinct images share the name caddy:latest:
$ container image ls | grep caddy
caddy latest 4b9bab58f2bf # build #2
caddy latest 844f60b64e47 # official
Expected
Per mocker's own ComposeService.buildTag(projectName:) in Sources/MockerKit/Compose/ComposeFile.swift (L368-370):
public func buildTag(projectName: String) -> String {
image ?? "\(projectName)-\(name):latest"
}
the tag should be laradock-caddy:latest. Observed is caddy:latest, so either buildTag is not on the path compose build takes, or projectName is not reaching it. Upstream docker compose produces <project>-<service> here for exactly this reason: an unprefixed service name is not a safe image identity.
Expected result:
$ mocker images | grep caddy
laradock-caddy latest ...
docker.io/library/caddy latest sha256:844f6
and FROM caddy:latest continues to resolve to the official image on every build.
Impact
- Silent, self-referential builds — output drifts from the Dockerfile with no error and no warning. Hardest part is that it looks like it works.
- A local unqualified tag shadows an upstream image for anything else on the machine that references it.
- Two projects each building a service named
caddy, nginx, redis, … collide on one tag.
Related: #69, #70, #71, #72 from the same session.
mocker version: 0.7.2 (Homebrew) — macOS, Darwin 25.5.0, arm64
Summary
When a compose service has
build:and noimage:, mocker tags the built image with the service name alone rather than<project>-<service>. If the service's Dockerfile has aFROMreferring to that same name — the very common case of a thin wrapper over an official image — the next build resolvesFROMto mocker's own previous output instead of the upstream base. Builds then stack on top of each other silently, and the local tag shadows the official image.Reproduced below: the second build of a
FROM caddy:latestDockerfile used the first build's manifest as its base.Reproduction
caddy/Dockerfile:Compose service (no
image:key):Build #1:
Resulting tag is
caddy:latest, colliding with the official image already present:Build #2,
--no-cache, same command:Line
#5is the defect: the base resolved tosha256:e2fa1a8d…, which is build #1's output, not the officialsha256:844f60b6…. TheCOPYwas applied on top of an image that already had it. Every rebuild adds another layer on the previous rebuild.Image store afterwards — two distinct images share the name
caddy:latest:Expected
Per mocker's own
ComposeService.buildTag(projectName:)inSources/MockerKit/Compose/ComposeFile.swift(L368-370):the tag should be
laradock-caddy:latest. Observed iscaddy:latest, so eitherbuildTagis not on the pathcompose buildtakes, orprojectNameis not reaching it. Upstreamdocker composeproduces<project>-<service>here for exactly this reason: an unprefixed service name is not a safe image identity.Expected result:
and
FROM caddy:latestcontinues to resolve to the official image on every build.Impact
caddy,nginx,redis, … collide on one tag.Related: #69, #70, #71, #72 from the same session.