Topic 2.2
Layers & the Build Cache
In one line
Every Dockerfile instruction creates a cacheable layer, and the ORDER you write instructions in is the single biggest lever on how fast your builds are.
Think of it like this
A stack of transparent sheets on an overhead projector. Each sheet (layer) adds some marks on top of the ones below. The final image you see is all sheets stacked together — but each individual sheet still exists on its own, and if you swap out just the TOP sheet, all the sheets below it are completely unaffected and don't need to be redrawn.
Key ideas
- 01
Each instruction in a Dockerfile (FROM, RUN, COPY, etc.) creates one new, immutable LAYER, stacked on top of the previous one. The final image is just the union of all its layers viewed together.
- 02
Docker caches each layer by its build INPUT — if an instruction and everything that produced its inputs are UNCHANGED since the last build, Docker reuses the cached layer instead of re-executing it, which can make a rebuild take milliseconds instead of minutes.
- 03
The cache invalidation rule that matters most: the MOMENT one layer changes, EVERY layer AFTER it in the file must be rebuilt too, even if their own instructions didn't change — because they're built ON TOP of that now-different layer.
- 04
This is why instruction ORDER matters enormously: put things that change RARELY (installing OS packages, installing dependencies) EARLY in the Dockerfile, and things that change FREQUENTLY (copying your actual application code) LATE — so a small code change only invalidates the last couple of cheap layers, not an expensive dependency-install layer.
- 05
The classic anti-pattern:
COPY . .(copying your ENTIRE project) followed by a dependency-install step. Since your source code changes on every commit, this invalidates the cache for the dependency-install step on EVERY single build, needlessly re-downloading dependencies every time — even though the dependencies themselves didn't change at all. - 06
docker build --no-cacheforces a full rebuild ignoring the cache entirely — useful when you suspect stale cache is hiding a real problem, but slow; use it deliberately, not as a habit.
In your stack
- →
The single highest-impact Java Dockerfile optimization: COPY just
pom.xml(orbuild.gradle) and runmvn dependency:go-offline(orgradle dependencies) BEFORE copying your actual source code. Dependencies change rarely; source code changes every commit — ordering it this way means most builds skip re-downloading the entire dependency tree.
Code & diagrams
Same end result, wildly different rebuild speed.
# BAD: any source code change invalidates the dependency-download layer too
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY . . # <- copies EVERYTHING, including source, first
RUN mvn dependency:go-offline # <- re-runs on EVERY code change, even trivial ones
RUN mvn package -DskipTests
# GOOD: dependency layer only invalidates when pom.xml itself changes
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml . # <- only the dependency manifest, copied first
RUN mvn dependency:go-offline # <- cached across builds as long as pom.xml is unchanged
COPY src ./src # <- source changes here, AFTER the expensive step
RUN mvn package -DskipTests # <- only this and the COPY above re-run on a code changeOne changed layer invalidates everything stacked above it.
Explain it without notes
Why does changing ONE line in your application's source code sometimes trigger a multi-minute dependency re-download during a Docker build?
If you reorder a Dockerfile so COPY . . happens BEFORE RUN npm install, what specifically breaks about caching, and how would you fix it?
Practice
Take the 'BAD' Dockerfile pattern from this topic, build it twice in a row with a tiny source change between builds, and time how long the second build takes. Then convert it to the 'GOOD' pattern and repeat — compare the times.
Run docker history <your-image> on any image you've built and read the layer list — note which layers are large and which are small.
Trade-offs
- ↔
Splitting COPY into multiple precise steps (manifest first, source second) adds a couple of extra lines to your Dockerfile versus a lazy single
COPY . .— a tiny readability cost for what's usually an order-of-magnitude build-speed improvement in any real project.
Done when you can
I order my Dockerfile instructions from least-frequently-changing to most-frequently-changing.
I can explain exactly why one changed layer invalidates every layer built on top of it.
I've personally timed a bad-vs-good caching order and seen the difference.