Skip to content

compose build tags image with bare service name — FROM <base> then resolves to mocker's own previous build #73

Description

@KennethWKZ

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions