Date: 2026-05-13 Prepared by: Copilot investigation agent
Fork this repository:
Why this repo:
- The observed behavior is consistent with the CLI build/metadata assembly path, not the spec intent.
- The likely fix point is in CLI source where metadata is collected and re-written.
Suggested fork workflow:
- Fork
devcontainers/cli. - Create branch
fix/preserve-user-dockerfile-metadata. - Add/adjust tests first, then patch implementation.
- Open PR to upstream
devcontainers/cliwith repro matrix and source trace below.
Your expectation is standard: image metadata in devcontainer.metadata is intended to support this exact use case.
Observed behavior in tested CLI build paths can drop user Dockerfile metadata entries from the effective final image label.
Working decision for downstream reliability:
- Treat runtime-critical settings as devcontainer JSON source of truth (workaround branch B1 in downstream repo).
- In parallel, fix/report upstream CLI behavior.
Reviewed references:
- devcontainers/spec issue #18 (Dev container metadata in image labels)
- devcontainers/spec PR #95 (Image Metadata Proposal, merged)
- containers.dev spec sections for Image Metadata and Merge Logic
Intent established by spec/discussion:
devcontainer.metadatalabel is an intended standard mechanism.- Label may contain object or array snippets, merged at runtime.
- Mounts merge rule is collected list, with conflict handling by source.
- Features and image metadata are meant to compose, not erase user intent.
Environment versions:
- devcontainer CLI: 0.87.0
- Docker: 29.4.3
- buildx: 0.33.0
- Node for CLI invocation: 22.11.0 via mise
Build path: docker build only (no devcontainer CLI)
Base: metadata-bearing (mcr.microsoft.com/devcontainers/base:debian)
Result: user metadata entry survived.
Build path: devcontainer CLI build, no features
Base: metadata-bearing (mcr.microsoft.com/devcontainers/base:debian)
Result: user metadata entry did not survive.
Build path: devcontainer CLI build, with features
Base: metadata-bearing (mcr.microsoft.com/devcontainers/base:debian)
Result: user metadata entry did not survive.
Build path: devcontainer CLI build, no features
Base: plain (debian:bookworm, no preexisting devcontainer metadata)
Result: user metadata entry survived.
Build path: devcontainer CLI build, with features
Base: plain (debian:bookworm)
Result: user metadata entry did not survive.
Interpretation:
- Feature-enabled CLI path consistently overwrote effective user label metadata in tests.
- CLI no-feature behavior appears sensitive to base metadata state.
- This indicates implementation/path behavior, not a spec prohibition.
name: "label-repro"mounts: ["source=${localEnv:HOME}/.aws,target=/home/vscode/.aws,type=bind"]postCreateCommand: "echo HELLO_FROM_LABEL"
mise x nodejs@22.11.0 -- npx -y @devcontainers/cli@latest build --workspace-folder <repro-folder> --image-name <image-tag> --no-cache
docker inspect --format '{{ index .Config.Labels "devcontainer.metadata" }}' <image-tag> | jqdocker history --no-trunc <image-tag> | grep -i 'devcontainer.metadata'
Note:
- Docker-in-Docker feature on Debian trixie required
"moby": falsefor build success in repro.
High-probability break path:
- Dockerfile config flow calls
buildNamedImageAndExtend(...)thenbuildAndExtendImage(...). buildAndExtendImage(...)callsgetImageBuildInfoFromDockerfile(...)before generating wrapper Dockerfile.- In
internalGetImageBuildInfoFromDockerfile(...), metadata source is derived viafindBaseImage(...)+inspectDockerImage(baseImage)and parsed from that image. - Wrapper metadata is then generated from computed metadata/features/config with
getDevcontainerMetadata(...)and written viagetDevcontainerMetadataLabel(...)into generated wrapper Dockerfile (Dockerfile-with-features/Dockerfile.extended). - Final image has a later
LABEL devcontainer.metadata=...write from wrapper path, which becomes effective (label replacement semantics).
Observed consequence:
- User Dockerfile metadata entry can be absent from final effective label, even when present in image history.
Primary objective:
- Preserve user Dockerfile metadata entries in effective final
devcontainer.metadatafor CLI build paths, especially with features.
Implementation direction:
- Ensure metadata computation for wrapper label includes metadata from the built user Dockerfile image layer (not only resolved base image metadata).
- Validate behavior across:
- Dockerfile config with features
- Dockerfile config without features
- image-based config extended with features
- Keep merge rules consistent with spec intent.
Add regression tests that assert final image effective metadata includes user Dockerfile metadata entry:
- Dockerfile + feature + metadata-bearing base.
- Dockerfile + feature + plain base.
- Dockerfile without feature + metadata-bearing base.
Assertions:
- Final inspect metadata contains user entry by marker (
nameor unique command). - Feature metadata still present.
- Mount and lifecycle fields are preserved/merged as expected.
In downstream repos relying on runtime-critical mounts/hooks:
- Move runtime-critical fields from Dockerfile label to devcontainer JSON.
- Keep Dockerfile metadata minimal/non-critical where possible.
- Add drift tests if maintaining parallel local/release configs.
Problem statement:
devcontainer buildcan produce finaldevcontainer.metadatathat excludes user Dockerfile metadata entries in certain build paths, especially when features are involved.
Expected:
- Final metadata should preserve user Dockerfile metadata and append/merge feature/runtime metadata according to spec merge model.
Actual:
- Final effective label can reflect wrapper/base/feature entries without user Dockerfile entry.
Impact:
- Mounts, lifecycle hooks, and other runtime-critical settings encoded in user image metadata may silently disappear.
Evidence:
- Repro matrix above + inspect/history outputs + version snapshot.
This document is the single source handoff for the next agent run in a forked devcontainers/cli context.