BuildKit & Build Performance
BuildKit & Build Performance
The legacy Docker build engine — the one invoked by docker build before BuildKit — was a sequential layer processor. It could not parallelize independent stages, it stored secrets in the image history, and its cache was coarse-grained. BuildKit, now the default engine since Docker 23.0, rewrites those rules from first principles. At Google, Meta, and every other large-scale shop, understanding the internals of BuildKit is not optional: a 90-second CI build that could be 18 seconds is real engineer-hours wasted daily at fleet scale.
How BuildKit Differs Architecturally
BuildKit represents a Dockerfile as a directed acyclic graph (DAG) of LLB (Low-Level Build) operations rather than a flat list of instructions. This graph is sent to the buildkitd daemon, which can schedule independent nodes in parallel, skip subgraphs whose outputs are already cached, and stream layers as content-addressable blobs. The result: multi-stage builds that are genuinely concurrent, not just apparently so.
Enabling BuildKit
Since Docker Engine 23.0, BuildKit is the default. On older setups or in CI environments, force it explicitly:
Cache Mounts: The Biggest Single Win
A cache mount (--mount=type=cache) attaches a persistent directory to a RUN instruction that survives across builds without becoming a layer. This is the canonical way to cache package managers. Without cache mounts, every go mod download or pip install re-downloads gigabytes of dependencies on every cache miss.
id + the current user. Set sharing=locked (one build at a time) or sharing=private (independent copy per build) when running concurrent builds in CI to avoid cache corruption. The default is sharing=shared — fine for most cases.
Build Secrets: Never in Layers
The pre-BuildKit workaround for secrets (ARG, ENV, multi-stage copies) always left the secret in at least one intermediate layer visible via docker history. BuildKit secrets are mounted as a tmpfs during the RUN instruction and are never written to any layer. This is the only production-safe way to consume credentials at build time.
docker history --no-trunc. At Google-scale internal package registries this is a live vulnerability category. Use --mount=type=secret exclusively.
SSH Agent Forwarding at Build Time
Private Git dependencies require SSH access during build. BuildKit provides --mount=type=ssh which forwards the host SSH agent socket into the build — no key files ever touch the image:
docker buildx & Multi-Platform Builds
buildx is the CLI plugin that exposes the full BuildKit API. Its most production-critical feature is multi-platform image builds — building linux/amd64 and linux/arm64 in a single command and pushing a multi-arch manifest. At AWS and GCP, arm64 (Graviton / Tau T2A) offers roughly 20–40% cost reduction for compute-bound workloads.
ubuntu-latest (amd64) runner and one ubuntu-latest-arm64 runner (GitHub Actions or self-hosted), build each platform natively, then use docker buildx imagetools create --tag ... --amend ... to merge the manifests. This is what large registries do internally.
Inline Cache and Remote Cache Backends
BuildKit can export its cache to a registry so that cold CI runners reuse prior build artifacts. With --cache-to and --cache-from, a fresh runner achieves near-warm-cache build times:
mode=min (default) only caches the final stage layers — useful when you want the smallest cache blob. mode=max caches every intermediate stage, producing better hit rates for multi-stage builds at the cost of a larger cache image. In monorepos with many shared base stages, mode=max almost always wins.
Production Failure Modes
- Cache poisoning via mutable tags:
--cache-fromwith a:latestcache tag that another job is simultaneously overwriting leads to non-deterministic builds. Pin cache tags to a branch or a stable ref. - Stale cache mounts in CI: BuildKit cache mounts are node-local. On ephemeral CI runners, the mount is always empty. Use registry cache backends, not local cache mounts, for CI.
- buildkitd OOM on large monorepos: Increase
buildkitdmemory limits and configure--oci-worker-snapshotter=overlayfson Linux for better layer deduplication. - Secret leakage via build args: See the warning above. Audit CI logs for
ARG-passed tokens — they appear in plain text in verbose build output.