Build Cache, Cache Mounts, Bind Mounts, Secret Mounts, SSH Mounts, and Cache Import/Export: Concepts, Architecture, and Mental Model
Build performance becomes trustworthy only when you can explain which state was reused. This lesson separates immutable instruction/result cache from mutable cache mounts, ephemeral bind/secret/SSH mounts, and external cache artifacts, then traces how BuildKit decides whether to reuse or execute each build vertex.
Learning objectives
- Explain the difference between BuildKit instruction/result cache, mutable cache mounts, ephemeral bind mounts, secret mounts, SSH mounts, and exported cache artifacts.
- Trace an LLB vertex from declared inputs through cache-key evaluation to either a cache hit or execution and then to an output result.
- Identify which inputs do and do not participate in cache invalidation, including the special treatment of build-secret contents.
- Explain why cache is an optimization hint rather than a trusted release identity or required source of correctness.
- Define the evidence needed to prove builder identity, hit/miss behavior, mount lifecycle, output digest, timing, and external-cache provenance.
1. The problem: “cached” is not one kind of state
A fast second build can be caused by several different mechanisms.
BuildKit may reuse an entire instruction result because its cache
key still matches. A RUN --mount=type=cache step may
execute again but avoid downloading dependency files because a
mutable cache directory survived. A bind mount may expose source
files temporarily without committing them to the resulting layer. A
secret or SSH mount may appear only for one RUN vertex
and intentionally disappear afterward. Finally, an external cache
may allow a completely different builder to import reusable results.
Treating all of those as “Docker cache” makes performance hard to
reason about and security failures easy to miss.
The engineering goal is not maximal cache hits. It is a build that remains correct if cache is empty, fast when trustworthy cache is available, and auditable enough that another engineer can explain why each expensive step did or did not execute. Release identity still comes from the produced image/index digest and declared inputs, not from the existence of cache records.
2. Mental model: vertex → key → hit or execution → result
flowchart TD
A[Dockerfile + context + build args + base identities] --> B[Frontend creates LLB graph]
B --> C[BuildKit vertex]
C --> D{Cache key matches reusable result?}
D -->|yes| E[Reuse immutable result]
D -->|no| F[Execute vertex]
F --> G[Optional bind/cache/secret/SSH mounts]
G --> H[New result + cache metadata]
E --> I[Exporter]
H --> I
I --> J[Image/local/registry/OCI output]
H --> K[Optional external cache export]
A BuildKit frontend translates the Dockerfile into a low-level build graph. Each operation has declared inputs. BuildKit uses those inputs plus operation metadata to decide whether a previous result can be reused. If it can, the operation may never run. If it cannot, BuildKit executes the vertex, potentially with temporary mounts, and stores a reusable result. An exporter then decides where the build result goes. External cache export is a second output path; it is not the release image itself.
3. Instruction/result cache: exact reuse of a previous result
For ordinary Dockerfile instructions, BuildKit compares the
instruction and relevant inputs with prior cache records. Once a
layer is invalidated, later dependent work typically needs
reevaluation. This is why placing stable dependency-resolution steps
before frequently changing application source can improve reuse. The
exact details depend on the operation: COPY,
ADD, and RUN with bind mounts include file
metadata checksums, while an ordinary RUN command is
not automatically rerun just because a remote package repository
changed.
That last rule is operationally important. A Dockerfile containing
RUN apt-get update && apt-get install ... can
remain cached on a later day if its declared inputs are unchanged.
“The repository has newer packages” is external reality, not
necessarily part of the cache key. If freshness is a requirement,
express an intentional invalidation/rebuild policy and preserve the
resulting package/base identity rather than assuming time alone
breaks cache.
4. Cache mounts: mutable performance data that survives executions
RUN --mount=type=cache creates a persistent directory
for tools such as package managers and compilers. Unlike an
instruction-cache hit, the RUN can execute while
reusing files in the mounted directory. Build correctness must not
depend on what happens to be there; Docker explicitly documents
cache mounts as performance-only state that another build may modify
or garbage collection may remove.
| Sharing mode | Behavior | Typical reasoning |
|---|---|---|
shared (default) |
Multiple writers can use the same cache concurrently. | Suitable when the tool tolerates concurrent writers. |
private |
A concurrent writer receives a separate cache mount. | Reduces interference at the cost of duplicated cache state. |
locked |
A second writer waits for the first to release the cache. | Useful for tools such as APT that need exclusive access to cache/database state. |
The cache id, target, ownership and sharing policy are
part of the design. A generic ID reused across unrelated projects
can become an accidental trust bridge.
5. Build bind mounts: temporary input without a committed source layer
A build bind mount exposes files from a context, stage, or image to
a RUN operation. It is read-only by default. If you
enable writes, those changes are discarded when the instruction
finishes; they do not become the result layer. This is useful when a
large source tree is needed only to generate an artifact and you do
not want a separate COPY layer containing all of it.
Bind mounts still participate in cache reasoning because BuildKit can checksum the mounted input metadata. They are not an escape hatch from deterministic inputs. The source still needs a bounded identity, as Chapter 08 established.
6. Secret mounts: ephemeral sensitive input, special invalidation rule
RUN --mount=type=secret exposes a secret as a file,
environment variable, or both for one build step without baking the
secret into the image layer. That is materially safer than
ARG, ENV, or COPY for
credentials. However, one cache rule is easy to overlook: the
contents of a build secret are not included in the cache
checksum. Changing a token value does not, by itself, force that
RUN step to execute again.
Secret metadata such as the secret ID and mount path does participate in cache invalidation. If a build intentionally produces different non-secret output when a secret rotates, you need an explicit non-secret invalidation signal such as a changed build argument or changed declared input. Better still, design the secret-authenticated step so its output identity is tied to an immutable remote revision rather than to “whatever the credential can access today.”
7. SSH mounts: agent access without copying private-key bytes
RUN --mount=type=ssh gives the build step access to an
SSH agent socket or key material supplied by Buildx. The standard
pattern is to forward an already-loaded agent rather than copying a
private key into the build context or image. The mount is ephemeral,
scoped to the operation, and can be marked
required=true so the build fails instead of silently
falling back when authentication is missing.
SSH authentication does not replace host-key verification. A secure
private-repository workflow still needs a trusted
known_hosts policy or equivalent host identity check.
Authentication answers “who am I?”; host verification answers “which
server am I talking to?”
8. External cache: portable reuse, separate artifact and trust domain
Each BuildKit instance has its own internal cache. External cache
backends let one build export reusable results and another import
them. Current Docker documentation lists inline,
registry, local, and gha as
major backends, with backend availability depending on the builder
driver and configuration; S3 and Azure Blob documentation remains
marked experimental. The mandatory course path uses
type=local because it requires no cloud account or
registry credential.
External cache is not a signed release artifact merely because it lives in a registry or OCI-shaped directory. Treat it as optimization input. Separate cache namespaces by trust domain, especially between trusted main/release builds and untrusted fork/PR work. A cache hit must never grant code a privilege it would not have on a clean build.
9. Evidence model for cache-aware builds
Buildx version, builder name, driver, BuildKit version, worker platform, frontend syntax.
Source/context hashes, base digests, relevant build args, mount source identities, dependency lock files.
Plain progress showing CACHED versus executed vertices, cache-mount IDs/sharing, cache import/export source.
Metadata-file result digest, local image ID or pushed digest, elapsed timing, and test/runtime evidence.
10. Small challenge: classify the state
A second build prints CACHED for the dependency-install
vertex. Another build executes the vertex but downloads nothing
because the package-manager cache directory is warm. A third builder
imports an external cache and skips the vertex entirely. Explain
which mechanism caused each speedup, what evidence proves it, and
which of those states should be trusted as the application’s release
identity. The correct final answer to the last question is: none of
them; release identity comes from the resulting immutable artifact
and its declared inputs.
Knowledge check
Does a warm cache mount mean the RUN instruction was itself cached?
No. The instruction can execute and still reuse files from a persistent cache mount.
If a BuildKit secret value changes, is that value automatically part of the RUN cache checksum?
No. Secret contents are intentionally excluded. Use an explicit non-secret invalidation signal when output must change with secret rotation.
Why must a cache mount be optional for correctness?
It is disposable performance state and may be empty, modified by another build, or garbage-collected.
What does sharing=locked protect?
It serializes writers for tools that cannot safely share the same cache concurrently.
Can an external cache be treated as the immutable release identity?
No. It is reusable build state. Verify the resulting image/index digest and declared inputs independently.
Official references and version notes
- Docker build cache — current cache model, optimization guidance, invalidation, and storage backends.
- Build cache invalidation — instruction matching, COPY/ADD/bind-input checksums, RUN behavior, and the rule that secret contents do not invalidate cache.
- Optimize cache usage — instruction ordering, small contexts, build bind mounts, cache mounts, and external cache patterns.
- Cache storage backends — inline, registry, local, GHA, and availability notes for other backends.
- Local cache backend — OCI-layout local cache export/import used by the mandatory checkpoint.
- Registry cache backend — separate cache artifacts, min/max modes, and driver/containerd-image-store requirements.
-
RUN --mountreference — bind, cache, tmpfs, secret, and SSH mount semantics and options. - Build secrets — passing and consuming secret/SSH mounts without baking credentials into image layers.
-
docker buildx build—--cache-from,--cache-to,--secret,--ssh, progress, metadata, and output behavior. - Build drivers — driver capabilities that affect cache backend availability and builder isolation.
- Docker Engine 29 release notes — current Engine baseline and bundled component updates.
- Buildx releases — current Buildx release history.
- BuildKit releases — current BuildKit and built-in Dockerfile frontend release history.
Version-sensitive statements were rechecked against primary
documentation on 2026-09-21. The course baseline
is Docker Engine 29.8.1, Buildx 0.37.1, BuildKit 0.33.0, and the
built-in Dockerfile frontend 1.27.0, but each executable lab
records the versions and builder driver actually present. Cache
backend support varies by driver and image-store configuration;
the mandatory path uses an isolated
docker-container builder plus the
local cache backend because it is free, inspectable,
and does not require registry credentials.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.