Articles
Docker Build Cache Interview Questions: Layers, BuildKit Mounts, and Reproducibility
Practice Docker build-cache interview questions with layer invalidation, BuildKit cache and secret mounts, CI cache availability, and artifact checks.
Your CI build took twelve minutes after a one-line source change. Someone moves COPY . . below npm ci, and the next build is fast. Did they fix caching? Maybe—but a strong Docker interview answer predicts which instruction invalidated which later layers, distinguishes a cached image layer from a package-manager cache mount, and asks whether the resulting artifact is actually reproducible. A fast cache hit is not proof of correctness.
This guide uses the official Docker build-cache documentation for cache behavior. The Dockerfile and questions below are original practice scenarios, not candidate reports or claims that a company uses these exact prompts. For related practice, Design CI/CD Build Caching asks you to reason about cache identity and artifact correctness.
Question 1: Which steps rebuild after a source change?
BuildKit considers each instruction in order and determines whether its cache entry can be reused. Once an instruction's cache is invalidated, subsequent instructions in that stage need rebuilding. A common slow Dockerfile copies the whole repository before installing dependencies:
FROM node:22
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
If any copied application file changes, the broad COPY . . input changes and npm ci sits downstream. Docker has to run it again even if package-lock.json is unchanged. A more useful layout copies package manifests first, installs dependencies, then copies source and builds:
FROM node:22
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
Now a change limited to application source can reuse the dependency-install layer, provided no earlier input changed and the builder can access the cache. A manifest or lockfile change invalidates npm ci and later instructions. This is a dependency-ordering decision, not a magic property of npm. The same logic applies to other ecosystems, but their manifests, lockfiles, scripts, and package caches differ.
Do not stop at “move COPY down.” Check .dockerignore: a broad context can include build outputs, local caches, or secrets that accidentally change the copy checksum or enter the image. The ignore file controls context sent to the builder; it is both a performance and exposure boundary. In a multi-stage build, follow the stages separately and ask whether a later COPY --from depends on the changed artifact. Describe the exact instruction chain instead of guessing from overall duration.
Question 2: What is actually part of a cache match?
Docker's cache invalidation rules distinguish instruction text and inputs. For COPY and ADD, BuildKit calculates a checksum from relevant file metadata and contents; modification time alone is not part of the checksum. For an ordinary RUN, the command string is a major cache input, but the builder does not re-run an external package query just to see whether a newer package appeared. RUN apt-get update can therefore be cached even though the upstream repository changed.
That behavior is useful and dangerous. A repeat build from the same declared inputs may be fast, yet mutable external repositories can make a cache miss produce a different image. Conversely, a cache hit can reuse an artifact when a hidden dependency changed. The correct interview framing is “Which inputs does the build declare, and which external state does it silently read?” A lockfile is a significant control for language packages, but it does not pin the base image, OS package repository, build tooling, or every transitive supply-chain input.
Base-image tags can move. If you need stronger repeatability, pin a digest and manage intentional updates. Use lockfiles and exact package versions where practical, and record the builder and provenance expectations. An image built with pinned inputs can still differ because timestamps, nondeterministic build scripts, architecture, or network services influence it. Reproducibility is a tested property of the full build, not a label earned by one --no-cache run.
A five-change cache prediction exercise
Assume the revised Node Dockerfile above, a stable base-image digest, and an available local BuildKit cache. The outcomes below are original predictions from Docker's documented rules; actual cache records depend on stage inputs and builder settings.
| Change before next build | Expected first affected step | What to check |
|---|---|---|
Edit src/server.js only | Broad source COPY . . | npm ci should reuse; build step should rerun |
Edit package-lock.json | Manifest COPY | Dependency install and later instructions should rerun |
| Touch source file without content change | Often no COPY invalidation from mtime alone | Verify relevant metadata/content and build log |
Change the RUN npm ci command | That RUN step | Later layers rebuild; compare resulting dependency tree |
| Build on fresh CI worker without imported cache | Earliest missing cache entry | Distinguish correct Dockerfile ordering from cache availability |
Read the build log with plain progress output if the default view hides details. Confirm what was cached and inspect the final artifact; a “CACHED” line proves reuse, not equivalence to the latest upstream package repository. Conversely, a cache miss may be appropriate because a declared input changed. Give the interviewer both a prediction and an observation that could falsify it.
Question 3: How is a BuildKit cache mount different from a cached layer?
A cached layer skips an instruction and reuses its resulting filesystem snapshot. A cache mount gives an instruction a persistent directory that can survive even when the instruction itself has to rerun. Package managers can reuse downloaded archives or indexes from a mount, so a changed manifest may still require npm ci or apt to execute, but less data may need fetching. The contents of the cache mount are a performance aid, not part of the final image by default and not a substitute for declaring dependencies.
For example, a BuildKit RUN --mount=type=cache,target=/root/.npm npm ci permits the npm cache to persist across build executions. The layer can be invalidated by a lockfile edit while the package download cache remains useful. Do not claim a cache mount makes npm ci disappear from the logs or guarantees offline success. Cache mounts can be empty, evicted, shared, or unavailable on a fresh worker. The package manager must tolerate that and produce a correct result from declared inputs.
An external cache is yet another boundary. CI workers often start clean, so exporting and importing BuildKit cache records can preserve reusable layers across jobs. That improves speed but introduces cache storage, retention, trust, and platform compatibility questions. A team should know who can write a shared cache and how the build verifies artifacts from untrusted contributors. The interview answer should separate “Dockerfile permits reuse” from “CI has a cache source to reuse.”
Question 4: Where do secrets belong during a build?
Not in ARG, ENV, a copied .npmrc, or an echoed shell command. Docker's build secrets guidance recommends secret mounts for credentials needed during build. A secret mount makes the credential available to the specific RUN instruction without baking its content into an image layer in the ordinary case. The build command, package manager, and shell can still leak it if they print or copy it, so inspect behavior at those boundaries.
Secret contents do not form part of the cache key. That has a surprising consequence: if a build step's output depends on a secret's value, rotating the secret alone may not invalidate the cached step. The solution is to avoid making output semantically depend on secret bytes where possible. If a change truly requires a rerun, use an explicit nonsecret cache-busting input whose value changes with the rotation or intentionally invalidate that step. Do not expose the secret itself in a build argument to force a miss.
Consider a private package token. The token authorizes the fetch; the package version should be pinned by a lockfile. If token A and token B access the same immutable package content, the secret need not change the artifact. If entitlements alter what package bytes are returned for the same version, the build has a hidden dependency and reproducibility is already compromised. That is an inference about dependency design, not a blanket statement about every registry.
Question 5: How would you fix a slow CI build without hiding a stale result?
Measure first. Capture build logs showing the first cache miss, durations, context size, and whether the worker imported external cache. Inspect .dockerignore and the Dockerfile's instruction order. Separate one-time cold builds from repeated builds on the same branch and cross-branch builds. If the slow step is a dependency install after a broad COPY, narrow the manifest copy. If the step reruns but spends time downloading, test a package-manager cache mount. If every step misses on a fresh worker, configure a trustworthy external cache and check whether it is actually imported.
Then verify correctness with an artifact-level comparison. Record the image digest, package inventory, test result, and relevant build inputs for a cached build and a deliberately clean build. A binary difference may have a legitimate timestamp cause, but it deserves explanation. A passing test alone may not detect a stale bundled asset. If the builder targets multiple CPU architectures, compare like for like; cross-platform caches and outputs are not interchangeable by assumption.
Avoid --no-cache as the permanent answer to every CI problem. It is a diagnostic tool and sometimes a deliberate release policy, but indiscriminate use discards useful reuse without fixing undeclared inputs. Conversely, a very fast build should not override a failed provenance or dependency check. Define the team's release contract first, then optimize within it.
Question 6: What does a concise interview answer sound like?
“I would locate the first invalidated Dockerfile instruction. COPY . . before dependency installation makes every source edit invalidate npm ci; copying manifests first lets that layer survive source-only edits. A BuildKit cache mount speeds a rerun by preserving package downloads, while an external cache makes layer records available to fresh CI workers. I would keep credentials in secret mounts, remember that secret contents do not invalidate cache, and compare final artifacts from cached and clean builds before calling the result reproducible.”
That answer names four distinct boundaries and a check. The interviewer can challenge it by changing a lockfile, a base digest, or a token. You should be able to say exactly which instruction re-executes and why.
| Practice question | Connection to this scenario |
|---|---|
| Design CI/CD Build Caching | Define cache keys and validate outputs. |
| Explain container image flow in CI/CD | Follow image build, registry, and deployment stages. |
| Design a Dockerized GPU test pipeline | Discuss large build inputs and test reproducibility. |
| Design a Dependency-Aware CI/CD Pipeline | Separate dependency identity from task ordering. |
| Build a Jenkins CI for graphics tests | Verify test artifacts in a containerized CI pipeline. |
Continue with DevOps engineer interview questions. Bring a real Dockerfile, predict five edits, and check each prediction in the build log and final image.
Sources and further reading
- Docker: Build cache — cache overview and instruction ordering.
- Docker: Build cache invalidation —
RUN,COPY,ADD, and secret behavior. - Docker: Optimize build caching — layer ordering, cache mounts, context and external cache.
- Docker: Build secrets — temporary secret mounts and credential handling.