Skip to content

fix(config): warn about ignored uid/gid/mode at creation, not at load - #14299

Open
ndeloof wants to merge 2 commits into
docker:mainfrom
ndeloof:fix/file-reference-warning-at-runtime
Open

ndeloof wants to merge 2 commits into
docker:mainfrom
ndeloof:fix/file-reference-warning-at-runtime

Conversation

@ndeloof

@ndeloof ndeloof commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

What this PR does, in one sentence

Warns that uid, gid and mode on a service-level configs: / secrets: reference are ignored when the container is created, and only when they really are ignored (a file: source), instead of at load time for every source.

Context

Compose currently reports, while loading the project, that these three attributes are "not supported outside Swarm mode and will be ignored". For a file: source that is true: the file is bind-mounted as-is. For a content: or environment: source it is a false positive, because the value is copied into the container and the ownership and mode are applied (#14288, #14283; #14297 filters those findings out after the load).

The underlying issue is that the loader cannot know the answer. Whether the attributes are honored depends on how the object reaches the container, and that is settled at creation. It is also not a fixed property of Compose: moby/moby#53368 lets the engine id-map bind mounts, which will make ownership on a bind-mounted file possible on newer daemons. A decision taken from the compose file alone would then be wrong for them.

What the PR brings

  • No warning for content: / environment: sources, which fixes the false positive.
  • The warning for file: sources is kept, with the same message, but now comes from create / up / run — once per service, including dependencies and the run target — rather than from every command that loads the project. docker compose config no longer prints it.
  • The check lives in one small predicate next to the code that injects or mounts these objects, so teaching it about an engine capability later is a local change, with no impact on loading or on the public load options.
  • External and driver-backed objects keep their existing error rather than getting a misleading warning on top.

This is an alternative to #14297 for the same issues. It trades the post-load filtering for a decision taken where the information exists.

Fixes #14288
Fixes #14283

🤖 Generated with Claude Code

uid, gid and mode on a service-level configs:/secrets: reference are applied
when the object is copied into the container (content: and environment:
sources) and dropped when it is bind-mounted (file: sources). That is a
property of how the container is created, not of the compose file, and an
engine able to set ownership on bind mounts would change the answer.

Report them from create(), next to the other creation-time warnings, once
per requested service, instead of from the load-time unsupported-attribute
check. The false positive on inline and environment sources goes away
without post-filtering findings against the loaded project.

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
@ndeloof
ndeloof requested review from a team as code owners October 8, 2026 06:42
@ndeloof
ndeloof requested review from docker-agent and glours October 8, 2026 06:42

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

Lower-confidence findings (not posted inline)

  • [low] pkg/compose/secrets.go:100 — fileReferenceOverridesIgnored returns true for missing/undefined config objects via zero-value map lookup (confidence: weak 52/100). In practice compose-go validates all references at load time so a non-empty Source absent from the project map should be impossible at runtime; the anonymous (empty Source) case is intentional and tested.

  • [low] pkg/compose/run.go:194 — warnIgnoredFileReferences may double-warn for the run-target service during compose run if WithServicesDisabled keeps the service in ServiceNames() (confidence: weak 45/100). If the compose-go implementation excludes disabled services from ServiceNames(), this is a non-issue; the e2e test would not catch duplicates since it uses OutputContains.

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

Comment thread pkg/compose/run.go
}

// the run target is created here, not by create(), which only handles its dependencies
warnIgnoredFileReferences(project, []string{service.Name})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] Possible duplicate uid/gid/mode warning for the run target service

prepareRun calls warnIgnoredFileReferences(project, []string{service.Name}) at line 194 after startDependencies returns. Inside startDependencies, project is locally reassigned via project = project.WithServicesDisabled(options.Service) (a new value, not a mutation of the caller's *types.Project), and then s.create(ctx, project, ...) is invoked on that disabled copy — which in turn calls warnIgnoredFileReferences(project, project.ServiceNames()).

Whether the run-target service produces warnings from both call sites depends on WithServicesDisabled: if it sets the service's Scale to 0 but keeps it in the Services map, project.ServiceNames() on the disabled copy still includes the run target, so create() warns for it and then prepareRun warns for it a second time. Only if WithServicesDisabled removes the service from the map entirely is there no duplication.

If duplication occurs, a user running docker compose run web would see uid/gid/mode warnings for web twice.

To harden this: verify the exact semantics of WithServicesDisabled, and if needed, guard the explicit prepareRun call with a check that create() did not already cover the run target — or rely solely on the create() call and remove the explicit one if WithServicesDisabled keeps the service in the map (since then create() already warned for it).

Confidence Score
🟡 moderate 57/100

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No duplicate: Project.WithServicesDisabled moves the service from Services to DisabledServices and deletes it from Services, so project.ServiceNames() in create() (called from startDependencies on the disabled copy) does not include the run target. Only prepareRun warns for it. Leaving the code as is.

Comment thread pkg/compose/create.go
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
@ndeloof
ndeloof requested a review from docker-agent October 8, 2026 06:54

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

This review covers only the commits pushed since 93ccf6abc129.

The single change in this incremental diff — adding || service.GetScale() == 0 to the guard in warnIgnoredFileReferences — is a correct and minimal fix for the previously-identified spurious warnings for scaled-to-zero services. GetScale() returns 1 when Scale is nil (the default), so the guard only suppresses warnings for services explicitly scaled to 0, where no container is created. No bugs introduced by the new lines.

@codecov

codecov Bot commented Oct 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@glours glours left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One regression to fix before merge, see the inline comment on run.go: docker compose run on a service scaled to zero no longer warns.

Comment thread pkg/compose/run.go
}

// the run target is created here, not by create(), which only handles its dependencies
warnIgnoredFileReferences(project, []string{service.Name})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warnIgnoredFileReferences(project, []string{service.Name}) re-reads project.Services[service.Name], but the container is created from the local service copy, where service.Scale = &one has been forced a few lines above. For a run target declared with scale: 0 or deploy.replicas: 0, the GetScale() == 0 skip fires and the warning is silently dropped, even though a container is created and its uid/gid/mode are ignored. Before this PR run did warn for these services, so it's a regression in that corner. The scale-zero skip is right for create/up, just not for the run target.

Repro (app.conf is any file):

services:
  tool:            # run-only service
    image: alpine
    scale: 0
    command: ["true"]
    configs:
      - source: app-config
        uid: "1000"
        mode: 0o440
  tool-replicas:
    image: alpine
    deploy:
      replicas: 0
    command: ["true"]
    configs:
      - source: app-config
        uid: "1000"
        mode: 0o440
  control:         # default scale
    image: alpine
    command: ["true"]
    configs:
      - source: app-config
        uid: "1000"
        mode: 0o440

configs:
  app-config:
    file: ./app.conf

docker compose run --rm control warns about uid/mode. docker compose run --rm tool and docker compose run --rm tool-replicas create the container with no warning. On the PR's base commit all three warn.

Minimal fix: keep the skip in warnIgnoredFileReferences, move the per-service body into a helper taking the service itself, and call that helper from prepareRun with the local copy:

func warnIgnoredFileReferences(project *types.Project, services []string) {
	for _, name := range slices.Sorted(slices.Values(services)) {
		service, ok := project.Services[name]
		// a service scaled to zero gets no container, so nothing is ignored
		if !ok || service.GetScale() == 0 {
			continue
		}
		warnServiceFileReferences(project, service)
	}
}

func warnServiceFileReferences(project *types.Project, service types.ServiceConfig) {
	for _, ref := range service.Configs {
		warnIgnoredFileReference(service.Name, "configs", types.FileReferenceConfig(ref), types.FileObjectConfig(project.Configs[ref.Source]))
	}
	for _, ref := range service.Secrets {
		warnIgnoredFileReference(service.Name, "secrets", types.FileReferenceConfig(ref), types.FileObjectConfig(project.Secrets[ref.Source]))
	}
}
// run.go
warnServiceFileReferences(project, service)

No double warning: startDependencies disables the target before calling create(), so it is not in project.ServiceNames(). A unit case with a service whose project entry has scale: 0 but is passed to warnServiceFileReferences with Scale=1 would cover it.

@jdreesen

Copy link
Copy Markdown

Confirming this also affects secrets with environment: sources on Compose v5.6.0:

secrets:
  tls_certificate_key:
    environment: TLS_CERTIFICATE_KEY
services:
  web:
    secrets:
      - source: tls_certificate_key
        target: /etc/nginx/ssl/nginx.key
        mode: 0400

docker compose run --entrypoint stat … -c '%a %n' /etc/nginx/ssl/nginx.key prints 400, so mode is applied (createTar in pkg/compose/secrets.go), yet the "not supported outside Swarm mode and will be ignored" warning is shown. Might be worth making sure the fix in this PR covers secrets: as well as configs:.

This branch has not been deployed

No deployments
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.

[BUG] mode attribute in configs works but raises an unsupported warning [BUG] Warns about uid/gid, but with inline configs they do work

4 participants