How does Docker layer caching work and how do you optimize for it?
Quick Answer
Docker caches each instruction as a layer. If that instruction and everything before it are unchanged, Docker reuses the cache. To optimize, put instructions that change least at the top and instructions that change most at the bottom.
Detailed Answer
Picture building a tower of colored blocks. If you want to repaint the fifth block, you have to remove every block above it, repaint the fifth one, and then stack fresh blocks on top. Docker layer caching works the same way. Each Dockerfile instruction creates a layer (a block), and if any layer changes, every layer after it must be rebuilt from scratch. That is why instruction order in your Dockerfile has a huge impact on build speed.
Docker layer caching is the system that stores the result of each build instruction as a read-only filesystem layer. When you rebuild an image, Docker walks through the Dockerfile line by line, comparing each instruction to what it cached last time. For COPY and ADD, Docker computes a checksum of the files being copied and compares it to the cached checksum. For RUN, Docker compares the exact command string. If the instruction matches and all parent layers also match, Docker skips the work and reuses the cached result. The moment any instruction causes a cache miss, every instruction after it runs fresh, even if those later instructions have not changed at all.
Under the hood, each layer is stored as a compressed tar file in the Docker storage driver, usually overlay2 on modern Linux. The layer is identified by a SHA256 hash of its contents. When Docker checks a build step, it looks up the combination of parent layer ID plus current instruction hash in its local cache. With BuildKit (the default builder since Docker 23.0), the cache supports more advanced tricks like cache mounts for package manager directories and remote cache sources stored in container registries. BuildKit can also run independent stages in parallel, so stages that do not depend on each other build at the same time.
In production CI/CD pipelines, layer caching is the single biggest lever for build speed. The golden rule is: order instructions from least-frequently changed to most-frequently changed. System package installs and dependency downloads rarely change, so put them first. Application source code changes on every commit, so put it last. For a Node.js app, copy package.json and package-lock.json before the rest of the source, so npm install stays cached unless dependencies actually change. For Python, copy requirements.txt first and run pip install before copying your code. In CI, push cache layers to a registry with docker build --cache-from or BuildKit inline cache so different CI runners can pull and reuse layers across pipeline runs.
A critical trap is that any change to a COPY instruction invalidates it and every layer below it, even if the following RUN command is identical. Accidentally copying a frequently changing file, like a build timestamp or git revision, early in the Dockerfile busts the cache for everything downstream. Another common mistake is combining too many operations in a single RUN step. While this reduces layer count, it means the entire step reruns if anything in it changes. The sweet spot is grouping logically related operations that change at the same rate. Also, always keep apt-get update and apt-get install in the same RUN instruction. If they are split, a cached apt-get update might produce stale package lists that cause version resolution failures when paired with a fresh install.