Skip to content

fix(cli)!: build a Dockerfile configuration in one build as the reference does - #70

Merged
aledbf merged 1 commit into
mainfrom
align-single-build
Nov 13, 2026
Merged

aledbf merged 1 commit into
mainfrom
align-single-build

Conversation

@aledbf

@aledbf aledbf commented Nov 13, 2026

Copy link
Copy Markdown
Member

Aligns how a Dockerfile-based dev container is built with the official CLI (TS buildAndExtendImage, singleContainer.ts). Breaking: see docs/migration.md.

Before (Go)

  1. build -f <workspace>/.devcontainer/Dockerfile.devcontainer.build --target <stage>. The file is written into the workspace when the final stage has no name, which fails on a read-only workspace. --push/--output were dropped when there were Features.
  2. With Features, a second build FROM that image (Dockerfile.extended), re-tagged; on up, tagged …-features.

After (Go = TS v0.90.0)

  • One combined Dockerfile-with-features is written to <cache>/container-features/<ver>-<ms>/, never the workspace. It holds the user's Dockerfile with its final stage named, then the dev_containers_target_stage stage that installs the Features and writes the devcontainer.metadata LABEL. With no Features, that stage only writes the label, as TS does.
  • One build with --target dev_containers_target_stage. The Dockerfile and the argv are byte-identical to what TS wrote and ran for the same configs (golden-checked).
  • Features and the lockfile are resolved before the build, as in TS.
  • --push/--output reach the one build.
  • docker history matches TS (the label is a LABEL instruction).

Go-only behavior kept, now in the single build: --secrets-file / --ca-bundle secrets, the sibling .dockerignore (#969), the final stage's own devcontainer.metadata (#1225), build contexts, and the build-auth bridge. build --omit-syntax-directive, declared but ignored before, now takes effect for Dockerfile and compose builds.

Breaking

  • up tags a Dockerfile config with Features vsc-<folder>-<hash>, as TS does, no longer …-features.
  • No <Dockerfile>.devcontainer.build file is written into the workspace.
  • An up Feature-fetch error on this path loses the install features: prefix.

Tests

  • TestBuildDockerfileBuildsOnce / TestUpDockerfileBuildsOnce assert one build, the cache-folder -f, the target, the tags, and an unchanged read-only workspace. On main they fail with permission denied; with a writable workspace they show two builds.
  • TestBuildDockerfileWritesTheReferenceDockerfile: byte-exact goldens of the Dockerfile and argv, compared with TS's output.
  • Unit tests for the stage, sibling .dockerignore, metadata label order, build flags, the non-BuildKit path, failures and the syntax directive.
  • Runtime parity: build.read-only-workspace-dockerfile-success, up.read-only-workspace-dockerfile-success.
  • Tests that asserted the two-step build are replaced. docs/DIVERGENCES.md now cites TestBuildDockerfileMetadataLabel (#1225) and TestBuildDockerfileKeepsTheSiblingDockerignore (#969).

Still different from TS (follow-ups): build --label applies to Dockerfile configs (TS ignores it there); no SELinux --security-opt label=disable for build contexts; build.args order comes from a Go map.

Validation (local, after rebasing onto main with #66)

  • task lint: pass.
  • task test:race with TMPDIR outside /tmp: pass.
  • task parity:contract: only the 2 known /tmp/.git cases fail.
  • task spec:compliance: pass (before the rebase).
  • test:e2e: all pass with a longer timeout; the 5m task budget ran out under parallel load. After the rebase, TestE2E_BuildUpExec_Dockerfile, TestE2E_SiblingDockerignore and TestE2E_UpSecretsFile pass.
  • PARITY_COMMAND=build task parity:runtime: 48 pass; 4 TS-timeout inconclusives pass alone.
  • PARITY_COMMAND=up task parity:runtime: the only failures are the /tmp/.git remoteWorkspaceFolder group, also present on main on this machine.

🤖 Generated with Claude Code

…ence does

build and up built a Dockerfile configuration in two steps: the user's
Dockerfile with --target <stage> (written as
<Dockerfile>.devcontainer.build next to it when its final stage had no
name), then, with Features, a second build FROM that image. The file
in the workspace failed a read-only workspace and was left behind on
an interrupted build, the intermediate image changed layering and the
cache, and --push/--output were dropped from the base build.

As the reference CLI's buildAndExtendImage, the CLI now writes one
combined Dockerfile-with-features (the user's Dockerfile, its final
stage named, then the dev_containers_target_stage stage that installs
the Features and writes the devcontainer.metadata LABEL, or only the
label with no Features) into the Features folder under the cache
folder, and runs one build with --target dev_containers_target_stage.
The Dockerfile and the build arguments are byte-identical to what
v0.90.0 writes and runs for the same configurations. Features and the
lockfile are resolved before the build, as in TS.

The Go-only behavior carries over to the single build: --secrets-file
and --ca-bundle secrets, a sibling <Dockerfile>.dockerignore (#969),
the final stage's own devcontainer.metadata entries (#1225), and build
contexts. build's hidden --omit-syntax-directive now takes effect.

BREAKING CHANGE: up tags a Dockerfile configuration's image with
Features vsc-<folder>-<hash>, as the official CLI does, instead of
vsc-<folder>-<hash>-features; the build no longer writes
<Dockerfile>.devcontainer.build into the workspace. docs/migration.md
lists both.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@aledbf
aledbf force-pushed the align-single-build branch from 6c4b1f9 to ce4a03c Compare November 13, 2026 02:08
@aledbf
aledbf merged commit ccb4ae9 into main Nov 13, 2026
13 checks passed
@aledbf
aledbf deleted the align-single-build branch November 13, 2026 02:34
aledbf added a commit that referenced this pull request Nov 13, 2026
The one-build path for Dockerfile configurations (#70) built its
Features stage without the config's containerEnv order, so those ENV
lines came out sorted there while image configurations kept the
declared order. A Dockerfile variant of the declaration-order parity
case compares the built image with the reference.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
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