How do BuildKit cache mounts, secret mounts, and cross-compilation change Dockerfile patterns for CI?
Quick Answer
Cache mounts keep package manager caches across builds without bloating image layers. Secret mounts inject credentials during build without writing them to any layer. Cross-compilation via BUILDPLATFORM and TARGETPLATFORM builds multi-architecture images from one Dockerfile without emulation. Together they make CI builds faster, safer, and multi-arch ready.
Detailed Answer
Think of a chef's kitchen with three upgrades. Cache mounts are like a shared pantry that stays stocked between shifts so the next cook does not have to re-buy ingredients. Secret mounts are like a recipe book the chef can read while cooking but that gets locked away before the dish leaves the kitchen. No customer ever sees the recipe. Cross-compilation is like a kitchen that can prepare both French and Japanese cuisine from the same recipe card by adjusting technique based on who ordered.
BuildKit is Docker's modern build engine, replacing the legacy builder. Cache mounts (--mount=type=cache) tell BuildKit to mount a persistent directory into a RUN step that survives across builds but never gets included in the final image layer. This is a game-changer for package managers: Go modules, npm packages, pip wheels, and apt archives get downloaded once and reused in later builds. Without cache mounts, developers either accept slow builds that re-download dependencies every time, or use fragile tricks like copying lock files early, which still re-downloads if the lock file changes. Secret mounts (--mount=type=secret) inject files like NPM tokens, private registry credentials, or SSH keys into a RUN step without them appearing in any image layer, build history, or intermediate container.
Under the hood, BuildKit implements cache mounts as named volumes managed by the BuildKit daemon. When a RUN instruction with --mount=type=cache runs, BuildKit mounts the named volume at the specified path. The command runs with access to cached data, then the volume is unmounted. The contents persist in BuildKit's storage between builds but are never committed to image layers. Secret mounts work similarly: BuildKit mounts a tmpfs with the secret file, the RUN command reads it, and the tmpfs is unmounted and never recorded. For cross-compilation, BuildKit sets BUILDPLATFORM (the host architecture, like amd64) and TARGETPLATFORM (the desired output, like arm64) as automatic build arguments. A Go binary can be compiled with GOOS and GOARCH set from TARGETPLATFORM without running an ARM emulator on an AMD64 CI runner.
At production scale, these features compound. A payments-api Go service that took 4 minutes to build drops to 45 seconds because go mod download results are cached across builds. The same Dockerfile produces linux/amd64 and linux/arm64 images in a single buildx command, enabling deployment to both x86 EC2 instances and Graviton ARM instances. Secret mounts eliminate the risk of accidentally pushing a Docker image that contains an NPM_TOKEN in a layer, a vulnerability that has caused real credential leaks in public registries. CI pipelines should use docker buildx bake for multi-service builds, which runs builds in parallel and shares cache across services.
The non-obvious gotcha is that cache mounts are local to the BuildKit instance. In CI systems where each build runs on a fresh runner, cache mounts are empty unless the team configures an external cache backend like a registry cache (--cache-to=type=registry) or a shared S3 cache. Another trap is that secret mounts require the BuildKit syntax directive (# syntax=docker/dockerfile:1) at the top of the Dockerfile. Older Docker versions or build systems that do not enable BuildKit silently ignore the mount directives, and builds fail because the secret file is missing.