How do you optimize Dockerfile instruction order for faster builds?
Quick Answer
This directly restates and reinforces the earlier layer-caching-order question from the images/builds topic: place instructions that change rarely (system package installation, dependency installation from a lockfile) before instructions that change frequently (application source code copies), so a typical day-to-day code change only invalidates the cheap, fast final layers, not the expensive dependency-installation step.
Detailed Answer
This is deliberately the same core technique as the images/builds topic's cache-ordering question. It is worth restating in the performance context, since it is consistently one of the most effective build-speed optimizations available. Interviewers sometimes ask about it from either angle: as a Dockerfile authoring best practice, or as a performance-troubleshooting scenario.
The pattern, once more
FROM python:3.12-slim
WORKDIR /app
# Rarely changes -- comes FIRST
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Changes on nearly every commit -- comes LAST
COPY . .
CMD ["python", "app.py"]
A code-only change (no dependency changes) invalidates only the final COPY . . layer — the potentially slow pip install step stays cached, since its own inputs (requirements.txt) haven't changed.
Measuring the actual impact
time docker build -t myapp .
# First build: 45s (full pip install)
# Change only application code, rebuild:
time docker build -t myapp .
# Second build: 3s (pip install layer cached, only final COPY + nothing after it re-executes)
This kind of before/after measurement is worth actually doing on a real project, since the improvement from correct ordering alone is often dramatic. It can be the difference between a rebuild taking tens of seconds to minutes, versus a couple of seconds, purely from reordering instructions with no other change.
Beyond ordering: separating dependency-resolution from dependency-installation
# Some ecosystems benefit from an even finer-grained split
COPY package.json package-lock.json ./
RUN npm ci # only re-runs if the LOCKFILE changes, not on every dependency version bump elsewhere
COPY . .
Copying just the lockfile or manifest, not the whole project, before running the install step is the specific technique that makes this ordering effective. Copying the entire project first would still tie the install layer's cache key to the entire project's content, even if the install step technically comes "before" other application-code-touching steps. This would defeat the purpose.
Using cache mounts for package manager caches (BuildKit feature)
RUN --mount=type=cache,target=/root/.npm \
npm ci
BuildKit's cache mounts (see the BuildKit question) provide an additional, complementary speed technique. They persist a package manager's own internal download or cache directory across builds, even when the layer itself isn't reused from cache — for example, because the lockfile changed. This means re-downloading already-fetched package versions is avoided, even when a full reinstall is otherwise triggered.
Why this is worth knowing this well for an interview
This single technique — recognizing that Docker's cache invalidates forward from the first changed instruction, and deliberately ordering instructions by "how often does this change" — is one of the most broadly useful pieces of practical Docker knowledge. It is also a common, concrete way interviewers probe whether a candidate has actually authored and iterated on real Dockerfiles, rather than only having read about Docker in the abstract.