Project context for Claude Code working in this repo.
Hybrid Go + Node.js. All Dockerfile transpilation lives in src/ (Node). Go (main.go, pkg/) is a thin BuildKit frontend that:
- Receives the user's Dockerfile from BuildKit
- Shells out to the bundled
dockerfile-xNode binary to transpile it - Hands the resulting standard Dockerfile to the upstream
dockerfile-frontend
When adding a feature, the answer is almost always Node.js. The Go layer only changes for new BuildKit-level capabilities.
Frontend ↔ CLI contract (pkg/build/dockerfile-x.go)
- CLI exit
0+ stdout = transpiled Dockerfile. - CLI exit
2+ JSON on stdout = structured error. Go inspects{"error":...}:"missing-file"→ Go pullsfilenamefrom the build context via LLB and retries (legacy mechanism for local files referenced from outside the context).- Any other code → surfaced as a build error.
- All structured errors must be emitted via
emitJsonErrorAndExitusing a constant fromsrc/errors/codes.js. Don't invent ad-hoc error shapes.
Source abstraction (src/sources/)
Every include reference (local, http, git+, oci://) is wrapped in a Source. Contract:
read()→ file contentjoinRelative(relPath)→ child source for a relative ref in the same domainkey()→ cycle-detection identitydisplayPath(dockerContext)→ label for# DOCKERFILE-X:STARTmarkersstageAliasBase(dockerContext)→ seed for stage-name slugfsPath()→ real fs path for local sources,nullotherwiseasync resolve()→ eagerly pin remote identity (git oid / oci digest) beforekey()is read
resolveSource(ref, ctx) is the only place that detects schemes. It enforces that Source.joinRelative is only called with a scheme-less relative or absolute path — any unrecognized URI scheme is rejected up front so a remote-served Dockerfile can't pivot subsequent INCLUDEs to a different scheme/host. New source backends just plug in here — never re-implement scheme detection in instruction handlers.
Recursion model (src/core/loadDockerfile.js)
loadDockerfile(source, fileContext) is the recursive entry point. Guards:
MAX_INCLUDE_DEPTH(default 32, override--max-include-depth/DOCKERFILEX_MAX_INCLUDE_DEPTH)- Cycle detection via
includeChainofsource.key()s
Both apply uniformly to local and remote sources.
Children of the root local Dockerfile resolve relative to dockerContext, not to the root file's dirname — a BuildKit limitation documented in the README. Implemented by giving the root LocalSource a relativeBase = dockerContext (src/core/rootLoadDockerfile.js). Don't put this branching back into instruction handlers.
- Framework: Mocha +
mocha-snap. Run withyarn test; regenerate snapshots withyarn test:update. - Snapshot fixtures: input in test/fixtures/, expected in test/snapshots/ named after the
describe/itblocks. - Fixture-based test runner: test/common/testFixture.js execs
node index.jsagainst a fixture and snaps the output. - Tests that spin up an in-process server (HTTP, git, OCI registry) must spawn the CLI via
child_process.spawn, notexecSync—execSyncblocks the Node event loop, which prevents the in-process server from accepting the CLI's request, causing a deadlock.
yarn build:ncc— bundle Node sources intodist/index.js(one file). Fast.yarn build:pkg— produce apkg-static Node binary indist-bin/.yarn build— both.make docker-build-dev— build + pushdevthefuture/dockerfile-x:dev. Use this for BuildKit-frontend e2e tests;# syntax = ...:devrequires a pushed image,--loadalone does not work.make docker-push— full multi-tag release. Don't run unless the user explicitly asks.yarn lint— currently broken (legacy ESLint config). Skip; no need to fix unless asked.
Final stage is alpine:3 with git, oras, ca-certificates, and docker-credential-ecr-login. The label moby.buildkit.frontend.network.none is not declared because the frontend fetches remote sources at compile time. Other credential helpers (gcloud, acr) are user-installable via a derived image.
To test as # syntax = devthefuture/dockerfile-x:dev:
- The BuildKit frontend container reaches the host via
172.17.0.1(Docker default bridge gateway).127.0.0.1is the container's own loopback. - Local registries need plain HTTP —
OciSourceauto-enables--plain-httpfor127.0.0.1/localhostand falls back automatically onserver gave HTTP response to HTTPS client.
- Don't read user credential files (
~/.docker/config.json,~/.aws/credentials,~/.netrc,.env, SSH keys). Probe via tool auth flows; nevercat/grep/Readthem. - Don't add backward-compatibility shims; this is a transpiler, not a public library.
- Existing utilities live in src/utils/ (
checkFileExists,ensureDir,shortHash,generateFilePathSlug) and src/lib/ (parseDockerfile,downloadWithCache,spawnCmd). Prefer these over hand-rolling.