Docker Layer Cache
A Docker layer cache is the store of previously built image layers that a build reuses when an instruction and the inputs it reads are unchanged.
A Docker layer cache is the store of previously built image layers that a build reuses when an instruction and every input that instruction reads are unchanged since an earlier build. When the builder finds a matching entry it skips running the instruction and takes the finished layer from the cache instead.
Everything else about layer caching follows from that one rule: which edits cost a rebuild, how far down the image a rebuild spreads, and why the same Dockerfile can hit on one machine and miss on another.
Definition
A Docker image is a stack of read-only filesystem layers plus a manifest that records their order. Each Dockerfile instruction that writes to the filesystem produces one layer, identified by the digest of its content. Instructions that only change image metadata, such as WORKDIR, ENV, LABEL, and EXPOSE, change the image config rather than adding a filesystem layer, and they still take part in cache matching.
Before executing an instruction, the builder computes a cache key for it. BuildKit, the default builder in Docker Engine since version 23.0, derives that key from three inputs:
- The digest of the parent layer that this instruction builds on.
- The text of the instruction after variable substitution, so an
ARGvalue that changes between builds changes the key of every instruction that references it. - The digests of any files the instruction reads out of the build context, including their file modes.
If a layer carrying that key exists in a cache the builder can reach, the instruction is a hit and the build log marks the step CACHED. Otherwise the instruction runs and produces a new layer, which is then written to the cache under the computed key. Docker documents the key derivation and the cache backends in its build cache reference, checked on 2026-08-13.
The cascade rule
The parent digest is part of every child key, so the layers form a chain. The first instruction whose key changes invalidates its own layer and every layer after it, whatever those later instructions contain and however unrelated they look. A single character edited in a file copied at step 3 of a twelve-step Dockerfile rebuilds steps 3 through 12.
This is why instruction order does more for build time than any other Dockerfile choice. Slow and stable work belongs near the top, fast and volatile work near the bottom, and the boundary between them is the line where the cache usually breaks.
What each instruction hashes
The cache key covers different inputs per instruction type. The table below lists the common ones and the change that most often moves the key.
| Instruction | What the cache key covers | Common invalidation trigger |
|---|---|---|
FROM | the resolved digest of the base image | the base image tag is republished and now resolves to a new digest |
RUN | the parent digest and the command string after substitution | any edit to the command, including whitespace, or any change above it |
COPY and ADD | the parent digest, the instruction text, and the contents and modes of every path matched | one byte changed in any matched file, a new file that matches the pattern, or a mode change |
ARG and ENV | the substituted value inside every instruction that references the variable | a value that differs per build, such as a commit SHA or a build timestamp |
RUN --mount=type=cache | the parent digest and the command string; the mounted directory stays outside the key | the layer still rebuilds on a miss, while the mounted directory survives and the work inside the layer is shorter |
COPY --from=stage | the parent digest and the digests of the files copied out of the named stage | a rebuild of the source stage that changes the copied output |
WORKDIR, LABEL, EXPOSE | the parent digest and the instruction text | a change to an instruction above them |
Two entries in that table surprise people. File modes count, so a chmod +x on a script invalidates the COPY that carries it even though no byte of content moved. And a cache mount is excluded from the key on purpose: the instruction still reruns, and the package manager or compiler directory behind the mount is still populated, so the rerun does less work than a cold one.
Where the cache can live
A layer cache is a store, and the store can sit in three places. A single build can read from more than one of them.
On the build machine. The builder keeps layers in its local store, on disk next to the daemon or inside the buildx builder instance. Lookups are local and cost nothing beyond a disk read. The store is tied to that machine, so a workflow that gets a fresh machine per build starts every build cold.
In a registry. A cache export writes cache manifests and blobs to a container registry, either alongside the image or under a separate tag, and a later build imports them. This is the type=registry backend, driven by --cache-to and --cache-from. It is portable across machines and costs a network round trip plus registry storage. An inline export (type=inline) embeds cache metadata in the image itself and covers only the layers of the final stage. A local export (type=local) writes the same data to a directory, which a workflow can then archive and restore through whatever file cache it already has.
On a dedicated builder. A persistent remote builder holds its own layer store and keeps it between builds, so every client that connects to that builder reads the same store. No export or import step sits in the build path, and the cache lives with the builder rather than with the client that ran the build. This is the model behind remote build caches.
The choice between the three is a trade between locality and portability. A local store is the fastest to read and the easiest to lose. A registry export survives any machine and pays for that with transfer time on every build. A dedicated builder keeps the store warm without a transfer, at the cost of running a builder that outlives the build.
What a layer cache does not cover
A layer cache is keyed by Dockerfile instruction inputs and stores image layers. A dependency cache, sometimes called an artifact cache, stores files under a key the workflow computes itself, usually a hash of a lockfile. The two solve neighboring problems and often appear in the same pipeline, and confusing one for the other leads to a build that restores a node_modules archive it then throws away inside the image.
Example
Take a small Node.js image. The instructions are ordered so that dependency installation sits above the application source.
# syntax=docker/dockerfile:1
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY src ./src
RUN npm run build
CMD ["node", "dist/server.js"]The first build runs every instruction and writes one cache entry per step. Run it again with no changes at all and every step is a hit:
#5 [2/6] WORKDIR /app
#5 CACHED
#6 [3/6] COPY package.json package-lock.json ./
#6 CACHED
#7 [4/6] RUN npm ci
#7 CACHED
#8 [5/6] COPY src ./src
#8 CACHED
#9 [6/6] RUN npm run build
#9 CACHEDNow edit one line in src/server.ts and build again. Step 5 reads the contents of src, so its key moves and the step runs. Step 6 sits below it and inherits the new parent digest, so it runs too. Steps 1 through 4 are untouched, and npm ci is still served from the cache even though the source tree changed.
Change package-lock.json instead, by adding a dependency, and the invalidation starts one step earlier. Step 3 reads that file, so step 3, step 4, step 5, and step 6 all run. The single lockfile edit costs a full dependency install plus a full application build.
The table below shows the state of each step on the second build for four kinds of change.
| Change since the last build | Step 3 COPY package.json package-lock.json | Step 4 RUN npm ci | Step 5 COPY src ./src | Step 6 RUN npm run build |
|---|---|---|---|---|
| Nothing | CACHED | CACHED | CACHED | CACHED |
One line in src/server.ts | CACHED | CACHED | runs | runs |
A dependency added to package-lock.json | runs | runs | runs | runs |
node:22-slim resolves to a new digest | runs | runs | runs | runs |
Read the table by column and the ordering rule becomes concrete. Move the two source instructions above the two dependency instructions and the second row collapses into the third: every source edit would reinstall dependencies, because the reinstall would then sit below a step whose key moves on every commit.
The fourth row is the case no Dockerfile edit prevents. A mutable base tag such as node:22-slim resolves to a new digest whenever the publisher pushes an update, and the whole image rebuilds from step 1. Pinning the base image by digest trades that rebuild for an explicit update commit.
The same build inside a GitHub Actions workflow
Nothing above changes when the build runs in GitHub Actions, except that the machine is usually new for every job, so the local store is empty at the start of every run. The cache has to come from somewhere the job can reach:
name: image
on:
push:
branches: [main]
jobs:
build:
runs-on: warp-ubuntu-latest-x64-4x
steps:
- uses: actions/checkout@v4
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: acme/api:${{ github.sha }}
cache-from: type=registry,ref=acme/api:buildcache
cache-to: type=registry,ref=acme/api:buildcache,mode=maxThree lines carry the caching behavior. runs-on names the label that routes the job to a machine. cache-from tells the builder where to look up keys before it runs an instruction. cache-to writes the resulting layers back, and mode=max exports the intermediate stage layers as well as the layers of the final image, which matters for any multi-stage Dockerfile whose build stage is the slow one.
Drop the cache-from line and every run of this workflow rebuilds from step 1, because the only store the job can reach is the empty local one on a machine that did not exist a minute ago. Cache backends and the actions that drive them are covered in the WarpBuild caching documentation, and the builder-side model, where the layer store lives with a persistent builder instead of a registry tag, is covered in the remote Docker builders documentation.
Related Terms
- Remote build cache: what it means for a layer cache to live off the build machine and how a build reaches it.
- Artifact cache: the file-level cache keyed by the workflow, and how its lifetime differs from a layer cache.
- Docker layer caching on GitHub Actions: keeping a layer cache alive across runs that get a fresh machine each time.
- Why your Docker layer cache misses every run: finding the first uncached instruction and the four changes that usually sit there.
- Remote Docker builders documentation: how a persistent builder keeps its own layer store between builds.
- WarpBuild pricing: per minute rates for runners and Docker builders.
FAQ
What is a Docker layer cache?
It is the store of previously built image layers that a build reuses when an instruction and every input that instruction reads are unchanged since an earlier build. The builder computes a key per instruction, looks the key up in the cache, and skips running the instruction when a layer with that key already exists.
What invalidates a Docker layer cache?
Any change to an instruction's cache key: the digest of the parent layer, the text of the instruction after variable substitution, or the contents and modes of the files that instruction reads from the build context. Because the parent digest is part of every child key, the first instruction that misses invalidates every instruction after it.
Where is the Docker layer cache stored?
In one of three places, and a single build can read from more than one. Layers can sit in the local store on the build machine, in a container registry as cache manifests and blobs written by a cache export, or on a dedicated builder that keeps its own layer store between builds and serves every client that connects to it.
Is a Docker layer cache the same as a dependency cache?
No. A layer cache stores built image layers keyed by the inputs of each Dockerfile instruction. A dependency or artifact cache stores files, such as a package manager directory, under a key the workflow computes itself. They have different keys, different lifetimes, and different restore paths.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.