Skip to content

fix(types): name the clashing service and make the error stable - #908

Open
AlbertEinsteinTG wants to merge 1 commit into
compose-spec:mainfrom
AlbertEinsteinTG:fix/container-name-clash-error
Open

fix(types): name the clashing service and make the error stable#908
AlbertEinsteinTG wants to merge 1 commit into
compose-spec:mainfrom
AlbertEinsteinTG:fix/container-name-clash-error

Conversation

@AlbertEinsteinTG

Copy link
Copy Markdown

Problem

When two services declare the same container_name, the error names neither
the conflicting service nor anything actionable:

services.a: container name "shared" is already in use by service {}"

Two defects in one line:

  1. {} instead of the other service's name.
  2. A stray unbalanced " at the end.

And a third, related issue: the message is not deterministic. The same
project reports a different service on each run.

Reproduce

services:
  a:
    image: alpine
    container_name: shared
  b:
    image: alpine
    container_name: shared

docker compose config, six consecutive runs, no changes in between:

services.a: container name "shared" is already in use by service {}"
services.a: container name "shared" is already in use by service {}"
services.a: container name "shared" is already in use by service {}"
services.b: container name "shared" is already in use by service {}"
services.b: container name "shared" is already in use by service {}"
services.a: container name "shared" is already in use by service {}"

Docker Compose v5.1.3 (vendoring compose-go v2.10.2). Reproduces on main.

Cause

types/project.go:

names := utils.Set[string]{}
for name, s := range p.Services {
    if s.ContainerName != "" {
        if existing, ok := names[s.ContainerName]; ok {
            return fmt.Errorf(`services.%s: container name %q is already in use by service %s"`, name, s.ContainerName, existing)
        }
        names.Add(s.ContainerName)
    }
}

utils/set.go:

type Set[T comparable] map[T]struct{}

existing is the map valuestruct{}{} — not the service that claimed
the name, and %s renders an empty struct as {}. A Set structurally cannot
carry the owning service, so this never worked. The trailing " looks like a
leftover from "%s" that lost its opening quote.

The non-determinism comes from range p.Services: Go randomises map iteration,
so which service is recorded first, and therefore which is reported as the
offender, varies per run.

Change

  • Track container_name -> declaring service in a map[string]string so the
    conflicting service can be named.
  • Print it with %q, which supplies the quotes and drops the stray one.
  • Iterate via ServiceNames() (already in this file, returns sorted names) so
    the reported pair is stable.

After:

services.b: container name "shared" is already in use by service "a"

Tests

The existing assertion stopped one word before the broken segment:

assert.Assert(t, strings.Contains(err.Error(), `container name "mycontainer" is already in use by`))

so everything after is already in use by was unverified — which is how both
defects survived. It now asserts the full message, which the stable ordering
makes possible.

Notes

  • No API change. CheckContainerNameUnicity's signature and error semantics are
    unchanged; only the message text and its stability.
  • The determinism half mirrors
    fix(interpolation): report all errors in a deterministic order
    same root cause (randomised map iteration producing a different message per
    run). Happy to split this into two commits if you'd prefer them reviewed
    separately.

CheckContainerNameUnicity tracked seen container names in a
utils.Set[string], which is a map[string]struct{}. Indexing it yields
the zero struct rather than the service that claimed the name, so the
%s verb rendered it as "{}". The format string also ended with an
unbalanced quote:

  services.a: container name "shared" is already in use by service {}"

Track container_name -> declaring service in a map[string]string and
print it with %q so both services are named. Iterate via ServiceNames()
instead of ranging over the map: Go randomises map iteration, so the
same project surfaced a different pair on each run, which made the
error unreproducible in bug reports and impossible to assert exactly.

  services.a: container name "shared" is already in use by service "b"

The existing test asserted a substring ending one word before the
broken segment, so neither defect was covered. It now asserts the
whole message, which the stable ordering makes possible.

Signed-off-by: Ben S George <73480087+AlbertEinsteinTG@users.noreply.github.com>
@AlbertEinsteinTG
AlbertEinsteinTG force-pushed the fix/container-name-clash-error branch from 054b901 to dd04613 Compare August 9, 2026 12:16
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