Docker Layer Caching
why every build reinstalls ALL your dependencies
Developer toolingdeveloper tooling / DevOps
The symptom
You change one line in app.py, rebuild, and Docker spends 4 minutes reinstalling 200 packages... again. Nothing about your dependencies changed. Why?
The Dockerfile doing it:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
Reasoning through it
Each instruction (FROM, COPY, RUN…) builds a cached layer. On rebuild Docker reuses a layer ONLY if that instruction and everything above it are unchanged. The moment one layer's input changes, that layer AND every layer after it rebuild from scratch.
Look at line 3: COPY . . copies your ENTIRE source tree — including app.py. So editing app.py changes the COPY layer's input → its cache busts → and the next line, RUN pip install, sits downstream of it → so it reruns too. You reinstall everything because you touched one source file.
The fix: order by change frequency
Copy the thing that rarely changes (your dependency manifest) BEFORE the thing that changes every commit (your code):
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt . # changes rarely
RUN pip install -r requirements.txt # cached unless requirements.txt changes
COPY . . # changes every commit
CMD ["python", "app.py"]
Now editing app.py only busts that last COPY. The pip install layer stays cached. Build drops from minutes to seconds.
The rule
Order instructions least-likely-to-change (top) → most-likely-to-change (bottom). Cache invalidation cascades DOWNWARD only — never upward — so put volatile steps last.
Gotchas that trip people up
- Same pattern for Node:
COPY package.json .→RUN npm ci→COPY . .. - A layer's cache key is file CONTENTS, not just names — editing requirements.txt rightly busts it.
- .dockerignore matters: if .git / node_modules aren't ignored,
COPY . .changes constantly and you lose caching anyway. - "Fast on my laptop, slow in CI" is usually an empty cache — enable BuildKit + --cache-from / registry cache.
Your exercise
Reorder this for caching:
COPY . .
RUN npm ci
What goes first, and which single file should you COPY before npm ci? Bonus: which BuildKit feature (hint: RUN --mount=type=…) lets npm's download cache survive EVEN when package.json changes?