What is missing
The reference computes a dockerfilePrefixContent for the Dockerfile it generates to install Features (src/spec-node/containerFeatures.ts:272-277, pinned oracle v0.87.0), and it is a four-way decision:
const syntax = imageBuildInfo.dockerfile?.preamble.directives.syntax;
const omitSyntaxDirective = common.omitSyntaxDirective;
const dockerfilePrefixContent = `${
omitSyntaxDirective ? ''
: skipDefaultSyntax ? (syntax ? `# syntax=${syntax}` : '')
: useBuildKitBuildContexts && !(imageBuildInfo.dockerfile && supportsBuildContexts(imageBuildInfo.dockerfile)) ? '# syntax=docker/dockerfile:1.4'
: syntax ? `# syntax=${syntax}` : ''}
ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder
`;
deacon emits no syntax directive of its own. It preserves whatever the user wrote, because merge_dockerfile_with_feature_stage copies the base document verbatim and a # syntax= line stays on line 1.
Reachability, measured rather than assumed
The injecting branch requires all three:
useBuildKitBuildContexts — BuildKit >= 0.8.0;
!skipDefaultSyntax — and skipDefaultSyntax = engine >= 23.0.0 (containerFeatures.ts:249-250, minDockerEngineVersion = [23, 0, 0]). So the Docker engine must be older than 23.0.0 (February 2023), or its version unparseable;
- the user's Dockerfile must not already declare a frontend >= 1.4.
On a current engine there is no divergence, and this was measured, not reasoned. A fixture whose Dockerfile pins # syntax=docker/dockerfile:1.2 and declares a local Feature was built with both CLIs on docker 29.7.2:
- deacon:
ERROR: failed to build: current frontend does not support --build-context. Named contexts are supported since Dockerfile v1.4.
- reference
@devcontainers/cli@0.87.0: the identical BuildKit error, {"outcome":"error"}.
Both honor the user's pinned 1.2 frontend and both fail. The hypothesis that deacon fails where the reference succeeds on a modern setup is refuted.
It is also inert on podman. podman version --format '{{.Server.Version}}' reports 4.9.3, which is below 23.0.0, so the reference takes the injecting branch there — but # syntax= selects a BuildKit frontend image, which buildah does not fetch, so emitting it changes nothing on podman.
So the divergence is confined to: BuildKit >= 0.8 with a Docker engine < 23.0.0. There, a Dockerfile with no syntax directive (or one below 1.4) builds under the reference and fails under deacon.
What has already landed
supports_build_contexts — the reference's supportsBuildContexts (dockerfileUtils.ts:263) — is now ported and measured, so the decision this gap needs is available. crates/core/tests/dockerfile_utils_parity.rs asserts it across all 91 cases, 26 of which are upstream's own supportsBuildContexts table, at zero divergences. That closes the #686 full-port obligation for this function.
What remains is only the caller.
Why it was not implemented with the port
Implementing it means reading two versions deacon does not read today — docker version --format '{{.Server.Version}}' and docker buildx version — on the build path, to gate a branch that cannot fire on any environment available for testing here. The decision could be table-tested hermetically against the oracle's expression, but the end-to-end consequence on a pre-23 engine would ship unverified. That trade was taken deliberately and is recorded rather than silently skipped.
Acceptance
What is missing
The reference computes a
dockerfilePrefixContentfor the Dockerfile it generates to install Features (src/spec-node/containerFeatures.ts:272-277, pinned oraclev0.87.0), and it is a four-way decision:deacon emits no syntax directive of its own. It preserves whatever the user wrote, because
merge_dockerfile_with_feature_stagecopies the base document verbatim and a# syntax=line stays on line 1.Reachability, measured rather than assumed
The injecting branch requires all three:
useBuildKitBuildContexts— BuildKit >= 0.8.0;!skipDefaultSyntax— andskipDefaultSyntax = engine >= 23.0.0(containerFeatures.ts:249-250,minDockerEngineVersion = [23, 0, 0]). So the Docker engine must be older than 23.0.0 (February 2023), or its version unparseable;On a current engine there is no divergence, and this was measured, not reasoned. A fixture whose Dockerfile pins
# syntax=docker/dockerfile:1.2and declares a local Feature was built with both CLIs on docker 29.7.2:ERROR: failed to build: current frontend does not support --build-context. Named contexts are supported since Dockerfile v1.4.@devcontainers/cli@0.87.0: the identical BuildKit error,{"outcome":"error"}.Both honor the user's pinned 1.2 frontend and both fail. The hypothesis that deacon fails where the reference succeeds on a modern setup is refuted.
It is also inert on podman.
podman version --format '{{.Server.Version}}'reports4.9.3, which is below 23.0.0, so the reference takes the injecting branch there — but# syntax=selects a BuildKit frontend image, which buildah does not fetch, so emitting it changes nothing on podman.So the divergence is confined to: BuildKit >= 0.8 with a Docker engine < 23.0.0. There, a Dockerfile with no syntax directive (or one below 1.4) builds under the reference and fails under deacon.
What has already landed
supports_build_contexts— the reference'ssupportsBuildContexts(dockerfileUtils.ts:263) — is now ported and measured, so the decision this gap needs is available.crates/core/tests/dockerfile_utils_parity.rsasserts it across all 91 cases, 26 of which are upstream's ownsupportsBuildContextstable, at zero divergences. That closes the #686 full-port obligation for this function.What remains is only the caller.
Why it was not implemented with the port
Implementing it means reading two versions deacon does not read today —
docker version --format '{{.Server.Version}}'anddocker buildx version— on the build path, to gate a branch that cannot fire on any environment available for testing here. The decision could be table-tested hermetically against the oracle's expression, but the end-to-end consequence on a pre-23 engine would ship unverified. That trade was taken deliberately and is recorded rather than silently skipped.Acceptance
omitSyntaxDirectiveandskipDefaultSyntax, table-tested against the oracle.docker:20.10-dindis the obvious vehicle and needs a privileged runner), or an explicit note in the ledger row that it remains unmeasured.parity/SPEC_STATUS.mdrow moves off open nonconformance.