Chapter 11Lesson 01~110 minutes

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.

Build cacheLLB/cache keysRUN mountsSecretsExternal cache

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.
Chapter 11 evidence baseline — verified 2026-09-21. Mandatory exercises use only synthetic source/data, a fake token, an ephemeral local SSH identity, isolated chapter-owned Buildx builders, a local cache directory, and public base images. At verification time Docker Engine 29.8.1, Buildx 0.37.1, and BuildKit 0.33.0 with built-in Dockerfile frontend 1.27.0 are the current course baselines, but every lab records the actual installed versions, builder driver, worker platform, and frontend behavior. Docker currently documents bind/cache/secret/SSH RUN mounts; cache sharing modes shared/private/locked; secret values excluded from cache checksum; and local/inline/registry/GHA cache backends with driver-dependent support. Cache is never treated as release identity, and no real credentials, production registry, production daemon, or broad prune operation is required.

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.

Cache is disposable performance state. Never make application correctness depend on a cache directory already containing a file. BuildKit is allowed to garbage-collect cache state, external caches can be unavailable, and different builders do not automatically share internal cache.

2. Mental model: vertex → key → hit or execution → result

BuildKit cache and mount lifecycle
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

Builder identity

Buildx version, builder name, driver, BuildKit version, worker platform, frontend syntax.

Inputs

Source/context hashes, base digests, relevant build args, mount source identities, dependency lock files.

Reuse

Plain progress showing CACHED versus executed vertices, cache-mount IDs/sharing, cache import/export source.

Output

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.

Next lesson

Next: Guided Hands-On Workflow and Core Operations

Observe all four mount classes and external cache movement in a disposable builder without exposing real secrets.

Knowledge check

Does a warm cache mount mean the RUN instruction was itself cached?

If a BuildKit secret value changes, is that value automatically part of the RUN cache checksum?

Why must a cache mount be optional for correctness?

What does sharing=locked protect?

Can an external cache be treated as the immutable release identity?

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 --mount reference — 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 and compatibility note

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.