Lesson 27 of 30
A .dockerignore file is usually framed as a build-speed and image-size nicety. It's actually load-bearing for cache correctness — an untracked node_modules or .git folder in your build context can silently bust a layer's cache on every single build, even when nothing about your source changed.
10 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
A Dockerfile runs `RUN apt-get install -y build-essential` in one layer, then a later, separate `RUN rm -rf /var/lib/apt/lists/*` in the next layer. What actually happens to the final image size?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The Dockerfile-instructions lesson in this track established the core rule: every instruction is a cached layer, and cache invalidation cascades downward from the first changed layer. This lesson goes deeper on two consequences of that rule that trip people up in practice — one about image size, one about cache correctness.
Layer bloat from uncombined RUN commands isn't really about "too many layers" as a raw count — it's that layers are immutable diffs, stacked. If one RUN installs a package and a later, separate RUN deletes files from it, the deleted bytes are gone from the final filesystem view, but the layer that added them is still there, underneath, at full size. The image ships both: the addition and the deletion, stacked, not a net-zero. Combining install-then-cleanup into a single RUN is what actually removes those bytes from the image, because then the deletion happens inside the same layer as the addition, before that layer is ever committed.
.dockerignore and cache correctness is the sharper issue, because it's invisible until it isn't. Docker computes a COPY instruction's cache key from a checksum of the files it's copying, out of the build context — not the Dockerfile's text, the actual file contents Docker packages up before the build even starts. An untracked node_modules (regenerated locally, contents shifting) or .git (its internal objects change on every commit) sitting in that context, uncovered by .dockerignore, changes what gets checksummed for a COPY . . layer — even when the actual application source is byte-for-byte identical to the last build. The result: a COPY . . layer's cache "invalidates" for reasons that have nothing to do with your code.
# .dockerignore
node_modules
.git
*.log
FROM node:20-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# without .dockerignore: node_modules is IN the build context
# every `pnpm install` locally regenerates node_modules with different timestamps/lockfile-resolved paths
# -> COPY . .'s checksum changes even when src/ hasn't -> cache miss -> RUN pnpm build reruns needlessly
# with .dockerignore excluding node_modules and .git:
# COPY . .'s checksum only reflects files that can actually affect the build
# unchanged source -> unchanged checksum -> real cache hit
The install layer above is already protected by copying the lockfile before the source (the pattern the earlier lesson covered). But COPY . . and anything after it are only actually cache-stable if the build context excludes files that change independently of your source — otherwise you've protected the slow layer from irrelevant changes while leaving the fast layers exposed to them anyway.
RUN does not shrink the image — it only hides them from the final filesystem view. The layer that added those bytes is still part of the image and still gets pulled/stored; only a cleanup that happens inside the same layer (same RUN, joined with &&) actually removes the bytes from what ships..dockerignore uses .gitignore syntax but does a genuinely different job. A file can be gitignored (never committed) and still be present on disk and included in the Docker build context — the two ignore files serve different tools and need to be kept in sync deliberately, not assumed to match.COPY's cache if any file it copies changed on disk — this is why a colleague's local node_modules reinstall, sitting in an unignored context, can make a teammate's identical-looking build behave completely differently..dockerignore correctness matter even in a multi-stage build — multi-stage solves "don't ship build tools," not "don't waste cache on irrelevant files.""Why did our build cache miss even though nobody touched the Dockerfile or the source code?" is a real, specific debugging question, and the strong answer names the build-context checksum mechanism and a missing .dockerignore entry as the likely cause — not "clear the cache and rebuild," which fixes the symptom once without explaining or preventing the next one.