Layer caching and .dockerignore

Each instruction is a cached layer

Every instruction in a Dockerfile produces a layervisible directly in the build log from earlier in this lesson, one Step N/6 per instruction. Docker caches each layer, and re-uses it on the next build if that instruction, and everything before it, hasn’t changed — skipping the actual work entirely when the cache applies.

The naive order — show the pain

COPY . . copies every file in the project, including application code that changes constantly. Docker’s cache invalidation cascades forward — if COPY . . sees any file has changed, that layer’s cache is invalidated, and so is every layer after it, including RUN pip install. Three builds, in order, tell the whole story — notice the RUN step’s own timing in each:

Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python3", "app.py"]
click "Build" to see the build log

The first build actually installs everything — that’s real work, and the RUN step is the slow one. Rebuild again with nothing changed at all:

Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python3", "app.py"]
click "Rebuild (nothing changed)" to see the build log

Every step, including the install, comes back from cache — fast, because nothing Docker checked actually changed. Now change requirements.txt itself (a real dependency change, not just an unrelated code edit) and rebuild once more:

Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python3", "app.py"]
click "Rebuild (after changing requirements.txt)" to see the build log

The slow install comes back the moment something the install step actually depends on changes. The naive ordering’s real problem isn’t that caching fails here — it’s that COPY . . sitting before RUN pip install means any file changing, including ones with nothing to do with dependencies at all, has this same cache-busting effect on the install layer.

The fix: copy dependencies first, separately

Copying just requirements.txt first, installing from it, and only then copying the rest of the project means the install layer’s cache only breaks when requirements.txt itself actually changes:

Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python3", "app.py"]
click "Build" to see the build log

Now rebuild after changing a line in app.py — unrelated to requirements.txt entirely:

Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python3", "app.py"]
click "Rebuild (app.py changed)" to see the build log

The install step is skipped entirely — unlike the naive ordering, an unrelated code change no longer touches the install layer’s cache at all, because COPY . . now sits after it instead of before.

.dockerignore — the other half of the caching problem

COPY . . doesn’t just risk invalidating the cache — it copies everything in your project directory into the build context, whether you actually want it in the image or not: .git/ (your entire version history), __pycache__/, a local .venv/, and — worth connecting directly back to the secrets warning from earlier in this lesson — potentially a .env file sitting right there in the project root.

.dockerignore works exactly like .gitignore — a plain list of patterns to exclude, this time from what gets sent into the build context at all, before any COPY instruction even runs:

.git
__pycache__/
*.pyc
.venv/
.env

With this file present, COPY . . never even sees .env or .git — they’re excluded before the build context is assembled, not filtered out afterward. This closes the specific gap from earlier: a .dockerignore entry for .env means there’s no COPY instruction to accidentally sweep it in, in the first place, regardless of how the rest of the Dockerfile is written.

It also has a real caching benefit of its own: __pycache__/ and other frequently-changing, irrelevant files no longer count as “the project changed” from Docker’s perspective, since they’re never part of the build context to begin with — one less thing that can spuriously invalidate a layer’s cache.

Check your understanding
1/4

What does Docker's layer cache let a rebuild skip?

Exercise · Graded (real Docker build)

Starting from this naive Dockerfile (COPY . . before RUN pip install), with a .git/ directory, a __pycache__/ directory, and a .env file sitting in the project: reorder it so the install layer's cache survives an unrelated code change, and add a .dockerignore excluding .git, __pycache__/, *.pyc, and .env. This is built for real, twice, against an actual Docker daemon — an unrelated line gets appended to app.py between the two builds automatically.