Docker interview questions: Dockerfiles, images, and layers

Reviewed by Mark Dickie · Last updated

11 questions on this page you can answer and see marked on the spot. Go to the first one

Docker images are read-only, layered filesystem snapshots built from a set of instructions in a Dockerfile, and they are the artifact every container runs from. For an interview you should know how each Dockerfile instruction maps to a layer, which instructions create new layers versus metadata, and how build caching works so you can order instructions to minimize rebuild time. You should also be comfortable explaining multi-stage builds, the difference between COPY and ADD, and how to keep final images small.

What does a Docker interview test about images and Dockerfiles?

Most questions probe whether you understand what happens during docker build at the layer level. Interviewers want to see that you can write a Dockerfile that is cache-friendly, produces a small image, and does not leak build secrets into a published layer.

ConceptWhat to know
Layer modelEach instruction (RUN, COPY, ADD) creates a new layer; FROM starts the base.
Build cacheDocker caches each layer by instruction + input hash. Changing an early instruction invalidates all downstream layers.
COPY vs ADDCOPY is the plain file copy. ADD adds auto-extract and remote URL fetch, which is why most teams avoid ADD.
Multi-stage buildUse multiple FROM lines and copy only the final artifact from a builder stage into a slim runtime image.
.dockerignoreControls what enters the build context. Skipping it sends your whole repo to the daemon and slows every build.

How do you order Dockerfile instructions for faster builds?

  1. Put instructions that change rarely at the top (FROM, system package installs).
  2. Copy only what the dependency install needs (package.json, requirements.txt) before running the install step.
  3. Run the install step (npm install, pip install) as its own RUN so the layer is cached when source code changes but dependencies do not.
  4. Copy application source last, since it changes on every commit and invalidates all following layers.
  5. Add a .dockerignore entry for node_modules, .git, and local env files so they never enter the context.

What is a multi-stage build and when should you use one?

A multi-stage build uses more than one FROM instruction so you can compile in a heavy build image and copy just the output binary into a small runtime image. The final image carries no compiler toolchain, no source files, and no intermediate artifacts. Use it whenever the build requires a toolchain that the runtime does not need, which is the case for Go, Java, Rust, and most compiled languages.

The quiz below covers these topics with real interview-style questions. Take it to find the gaps before the real thing.

Key facts

  • Tarmac's Docker interview questions cover 24 questions at difficulty 1–5 of 5.
  • Tarmac tracked 3,162 job postings asking for Docker in September 2026.
  • Roles asking for Docker advertise a median base salary of £77,500, across 1,105 job postings as of September 2026.
  • Tarmac last reviewed these Docker interview questions on 13 October 2026.

At a glance

Questions24
Difficulty1–5 of 5
FormatsTrue / false, Fill in the blank, Short answer, Multiple choice, Ordering, Code output

What you'll review

  1. dockerfile

Practice questions

Try one before you open the answer. Pick an option and press Check; it's marked on the spot.

True or False: In a Dockerfile, the RUN instruction executes a command at build time and commits the result into a new image layer, whereas the CMD instruction provides the default command to run when a container starts from that image.#

Options

Show answer

The statement is true: in a Dockerfile, RUN executes a command at build time and commits its filesystem changes as a new image layer. CMD does not run during the build; it sets the default command executed when a container starts from the image.

Why:

RUN executes during docker build, and each RUN instruction produces a new image layer containing the filesystem changes it made. CMD does not execute during the build; it records the default command that runs when a container is started from the image, and that command can be overridden at docker run. Because both descriptions match Docker's behavior, the statement is true.

In a Dockerfile, the instruction that specifies the base image to build from is written as _____ ubuntu:22.04.#

Show answer

In a Dockerfile, the instruction that specifies the base image to build from is written as **FROM** ubuntu:22.04.

Why:

The FROM instruction is the first required instruction in a Dockerfile and defines the base image that all subsequent layers build upon. In the example FROM ubuntu:22.04, the image ubuntu:22.04 is pulled as the starting point for the build.

Which Dockerfile instruction initializes a new build stage and sets the base image that subsequent instructions build on top of?#

Show answer

FROM. It sets the base image for the build stage, and each later instruction in that stage builds on top of it. A multi-stage Dockerfile starts a new stage with another FROM.

Why:

The FROM instruction sets the base image and starts a build stage; it is the required starting point for a Dockerfile, apart from ARG directives that may precede it. RUN executes build-time commands, COPY transfers local files, and CMD specifies the default container runtime command, so none of those initializes a stage or sets the base image.

Apart from comments, parser directives, and ARG instructions, the first instruction in every valid Dockerfile must be _____, which specifies the base image that all other instructions build upon.#

Show answer

Apart from comments, parser directives, and ARG instructions, the first instruction in every valid Dockerfile must be FROM, which specifies the base image that all other instructions build upon.

Why:

The FROM instruction is the first required instruction in every Dockerfile. It names the base image (for example, FROM python:3.12) that subsequent layers are applied to. Without it, the Dockerfile has no starting filesystem to build on. Comments, parser directives, and ARG instructions may appear before it, which is why the prompt excludes them.

Given a BuildKit build (using # syntax=docker/dockerfile:1) whose previous build is fully cached, the previous Dockerfile was: [Previous Dockerfile] FROM alpine:3.19 / RUN apk add --no-cache curl / COPY app.tar.gz /app/ / RUN tar xzf /app/app.tar.gz -C /app The modified Dockerfile being rebuilt is: [Modified Dockerfile] FROM alpine:3.19 / RUN apk add --no-cache curl bash / COPY app.tar.gz /app/ / RUN tar xzf /app/app.tar.gz -C /app The file app.tar.gz on disk is byte-for-byte identical to the previous build. Which instructions are cache hits vs. cache misses under BuildKit, and how does replacing the COPY with COPY --link app.tar.gz /app/ change the outcome?#

Show answer

With a plain COPY app.tar.gz /app/ inside the modified Dockerfile: 1) FROM alpine:3.19 — cache hit (same base image reference/digest). 2) RUN apk add --no-cache curl bash — cache miss: the command string changed (added bash), so the cache key no longer matches the previous RUN apk add --no-cache curl. 3) COPY app.tar.gz /app/ — cache miss: a plain COPY's cache key includes the parent layer ID; since the RUN above missed cache (producing a different layer), the parent changed even though app.tar.gz is byte-identical. 4) RUN tar xzf /app/app.tar.gz -C /app — cache miss: its parent (the COPY layer) changed. With COPY --link app.tar.gz /app/ instead: 1–2 are the same (FROM = hit, RUN = miss). 3) COPY --link app.tar.gz /app/ — cache hit: --link creates the layer directly on top of the base image (the FROM layer), bypassing the intermediate RUN layer. Its cache key is computed from the base image ID and the checksum of app.tar.gz alone, not from the preceding RUN layer. Since both are unchanged, this is a cache hit. 4) RUN tar xzf /app/app.tar.gz -C /app — cache hit: because the COPY --link layer has the same digest as in the previous build (it was a cache hit), this RUN's parent layer is unchanged, so it also hits cache. In short, COPY --link breaks the cache-invalidation chain: without --link, a change in an earlier RUN invalidates all subsequent layers; with --link, the COPY layer's cache key depends only on the base image and the copied file's checksum, so it (and everything after it) can hit cache even when intervening layers changed.

Why:

The RUN instruction is a cache miss because BuildKit includes the command string in the cache key; adding bash changes that string. Without --link, a COPY instruction's cache key includes the parent layer ID, so when the RUN misses (producing a different layer), the COPY and everything after it also miss — even though app.tar.gz is byte-identical. COPY --link changes this by creating the layer on top of the base image rather than the immediately preceding layer, so its cache key is based on the base image and the file checksum alone. This lets the COPY --link hit cache despite the changed RUN, and because that layer digest is unchanged, the subsequent RUN tar also hits cache.

In a Dockerfile using BuildKit, a RUN --mount=type=cache,target=/cache instruction mounts a persistent cache directory. Which statement about that directory's contents is correct?#

Options

Show answer

The contents of a BuildKit cache mount are excluded from the resulting image and are not part of the RUN instruction's build cache key. BuildKit keeps them in its own cache storage, so they only speed up re-execution after a cache miss, such as reusing downloaded packages, and they do not bloat the image.

Why:

A cache mount is a persistent, writable directory managed by BuildKit's cache storage. Its contents are not captured in the layer's filesystem diff, so they never end up in the image. The cache key for the RUN instruction is built from the command string, the parent layer, and the mount specification (type, target, id, sharing), not from the files inside the mount. The mount only speeds up re-execution after a cache miss, for example by letting pip reuse downloaded packages. The option claiming the contents are saved into the layer is wrong because the mount is excluded from the layer diff, and the options claiming the contents are hashed into the cache key or copied into the image at the target path are both wrong for the same reasons.

Which Dockerfile instruction sets the base image that the image build starts from?#

Options

Show answer

The FROM instruction sets the base image that a Dockerfile build starts from. Every Dockerfile needs a FROM, and it must come before any instruction other than ARG. Later instructions such as RUN and COPY add layers on top of that base image.

Why:

The FROM instruction declares the base image, and every Dockerfile needs one to begin the build. Each later instruction adds a layer on top of that base. RUN executes a command during the build, COPY copies files from the build context into the image, and ENTRYPOINT sets the executable that runs when a container starts. Only ARG instructions may appear before FROM.

You need to copy a local requirements.txt from the build context into the image. Docker's own best-practice guidance says to prefer one instruction here. Which, and why?#

Options

Show answer

Prefer COPY for copying local files into an image. COPY only copies files and directories from the build context, so its behaviour is transparent and predictable. ADD does extra implicit work — it can fetch remote URLs and auto-extracts local tar archives — which makes builds surprising. Both preserve permissions and handle single files, so reserve ADD for the narrow case where you actually want local-archive extraction.

Why:

Docker's Dockerfile best practices recommend COPY for plain file copies because its behaviour is transparent: it only copies local files/directories from the build context. ADD does extra implicit work — it can download remote URLs and will auto-extract local tar archives — which makes builds less predictable and can mask surprises. Both preserve permissions and both handle single files. Reaching for ADD 'just in case' is the misconception; use ADD only when you specifically want local-archive auto-extraction.

You are building a Node.js application image and often rebuild it after changing only application source files (not package.json). Which Dockerfile instruction ordering lets Docker's layer cache skip the npm install step on those rebuilds?#

Options

Show answer

The ordering that keeps npm install cached is copying package.json first, running npm install, and only then copying the rest of the source. Docker invalidates a layer only when its inputs change, so source-only edits leave the dependency layer intact and skip the reinstall.

Why:

Docker caches each instruction as a layer and reuses it when its inputs are unchanged. Copying only package.json before running npm install means the install layer's input stays the same when source files change, so it is served from cache, and only the final copy of the source is re-executed. Copying all source before the install, or copying the source before running the install even when package.json is copied first, changes the input of the install layer on every source edit, so the install reruns each time.

The _____ instruction in a Dockerfile sets the working directory for all subsequent RUN, CMD, ENTRYPOINT, COPY, and ADD instructions. If the specified directory path does not already exist, Docker creates it automatically.#

Show answer

The WORKDIR instruction in a Dockerfile sets the working directory for all subsequent RUN, CMD, ENTRYPOINT, COPY, and ADD instructions. If the specified directory path does not already exist, Docker creates it automatically.

Why:

The WORKDIR instruction establishes the working directory for subsequent instructions in the Dockerfile. Unlike using RUN cd /app && ..., where the cd only applies within that one shell, WORKDIR persists across all following instructions. If the target directory does not exist, Docker creates it automatically, so an explicit mkdir is unnecessary. This makes WORKDIR the canonical way to set the filesystem context for a build stage.

A Node.js Dockerfile must install dependencies deterministically from the lock file, and it should maximize build-cache reuse when only application source code changes. Put these three instructions in the order they should appear in the Dockerfile.#

Put these in order

Show answer

In a Node.js Dockerfile, the cache-friendly order is to copy package.json and package-lock.json first, run npm ci, and then copy the application source with COPY . . The install layer stays cached until the manifests change, so a source-only edit rebuilds just the final copy step, and npm ci still installs strictly from the lock file.

Why:

Copying package.json and package-lock.json first, then running npm ci, makes the install deterministic because npm ci installs strictly from the lock file and fails if it is out of sync. Docker invalidates that install layer only when the manifests change, so it stays cached through source-only edits. Copying the full source with COPY . . last means a source change busts only the final layer. Placing the source copy before npm ci would invalidate the install layer on every source change, so it must come after.

A Dockerfile contains the instruction COPY . . to copy all application source files into the image. Name the file you should create in the build context root to keep paths such as node_modules and .git out of the build, and explain what it reduces or prevents during docker build.#

Show answer

Create a .dockerignore file in the root of the build context to exclude files such as node_modules and .git from being sent to the Docker daemon, reducing build context size and preventing unnecessary cache invalidation.

Why:

The .dockerignore file sits at the root of the build context and tells the Docker client which paths to exclude when it sends the context to the daemon during docker build. Without it, every file under the context directory, including large node_modules folders, .git history, secrets and IDE configs, is packaged and transferred, which slows the build and can leak sensitive data. Excluding these paths keeps the context small, speeds up the transfer, and stops unrelated file changes from invalidating the layer cache for the COPY instruction.

In a Dockerfile, which instruction copies a local .tar.gz archive from the build context into the image and automatically extracts its contents? Answer with the instruction keyword. (The similar COPY instruction does not extract archives.)#

Show answer

ADD. The ADD instruction auto-extracts local tar archives (such as .tar, .tar.gz, and .tar.bz2) from the build context into the destination path, whereas COPY copies files verbatim without unpacking them.

Why:

ADD is the only Dockerfile instruction in this pair that unpacks a local tar archive during the build: when the source is a recognized compressed or uncompressed tar file in the build context, Docker extracts it into the destination directory. COPY always copies the file as-is, so it would leave the .tar.gz intact. Because this implicit extraction can be surprising, Docker's best-practice guidance recommends using COPY for plain file copies and reserving ADD for cases where auto-extraction is actually needed.

You have a Node.js project with package.json, package-lock.json, and application source files. Which Dockerfile snippet leverages Docker's layer cache MOST effectively so that npm install is NOT re-run when only application source code changes?#

Options

Show answer

Option B

Why:

Docker builds each instruction as a layer and caches it keyed by the instruction text and the contents of any files copied in. In Option B, package.json and package-lock.json are copied first, so the npm ci layer is cached as long as those two files are unchanged. Only after that are the application source files copied. When source code changes, the cache is invalidated only from the second COPY . /app/ onward — npm ci stays cached and is not re-run. In Option A, COPY . /app includes source files, so any source change invalidates the cache for that layer and every subsequent layer including npm ci. In Option C, COPY . /app is first, so the same invalidation occurs before npm ci. In Option D, npm ci runs before package.json is even copied into the image, so it would fail or install nothing useful.

Consider this Dockerfile, built with the default BuildKit builder on amd64 from the locally available alpine:3.18 base image:#

Show answer

The WORKDIR /app/build/output instruction creates /app/build/output if it is missing, and it also creates the missing intermediate parents /app and /app/build, behaving like mkdir -p. It then sets /app/build/output as the working directory for the following instructions, so RUN pwd prints /app/build/output with no error.

Why:

WORKDIR does two things: it ensures the target directory exists, creating any missing parent directories along the path (equivalent to mkdir -p), and it sets that path as the working directory for subsequent instructions. Since none of /app, /app/build, or /app/build/output exists in the base image, WORKDIR creates the whole chain without needing a RUN mkdir. The RUN pwd step therefore runs inside /app/build/output and prints /app/build/output with no error.

In a Dockerfile, each instruction such as COPY, RUN, or ADD creates a cached _____. To maximize cache reuse during repeated builds, copy files that change rarely (e.g., package.json) before copying _____. When an earlier instruction's inputs change, all _____ layers must be rebuilt because the cache is invalidated from that point onward.#

Show answer

In a Dockerfile, each instruction such as COPY, RUN, or ADD creates a cached layer. To maximize cache reuse during repeated builds, copy files that change rarely (e.g., package.json) before copying source code. When an earlier instruction's inputs change, all subsequent layers must be rebuilt because the cache is invalidated from that point onward.

Why:

Each Dockerfile instruction produces a layer, and Docker caches each layer keyed on the instruction and its inputs. Copying rarely-changing files such as dependency manifests first keeps those layers cached, so dependency installation is not repeated when only application source code changes; source code is therefore copied after the manifests. When an earlier instruction's inputs change, the cache is invalidated from that point forward, so all subsequent layers must be rebuilt.

A Node.js project lives in a Git repository containing package.json, package-lock.json, and a src/ directory with application code, including src/server.js. It is built with this Dockerfile:#

Show answer

The FROM and WORKDIR layers are cache hits because their instructions and inputs are unchanged. The COPY . . layer is a cache miss because Docker keys a COPY layer on checksums of the files it copies, and src/server.js changed. Docker invalidates every later layer once an earlier layer misses, so RUN npm ci and CMD are also misses and npm ci runs again even though package.json and package-lock.json did not change. The standard fix is to copy the package manifests first: COPY package*.json ./, then RUN npm ci, then COPY . .. The dependency layer then depends only on the package files, so it stays cached unless dependencies change.

Why:

Docker builds layers top to bottom and reuses a layer only if its instruction and its inputs are unchanged, and it stops reusing the cache from the first changed layer onward. FROM and WORKDIR are unchanged, so they hit. COPY . . copies the whole build context, and its cache key is a checksum of those files, so the edited src/server.js makes it miss. That miss invalidates the following RUN npm ci and CMD instructions, so npm ci re-runs even though the lock file did not change. Copying package*.json and running npm ci before copying the rest of the source makes the install layer depend only on the manifests, so source-only edits reuse it.

Consider the following multi-stage Dockerfile:#

Options

Show answer

The file contains Running on Alpine (trailing space, no version). A global ARG declared before FROM is available in FROM instructions across all stages but is not available inside a build stage's RUN/COPY instructions unless re-declared with a bare ARG in that stage. The second stage never re-declares VERSION, so Docker substitutes an empty string. The build succeeds; undefined ARG substitutions resolve to empty rather than erroring.

Why:

An ARG declared before the first FROM is a global build argument: it is available for substitution in every FROM instruction across all stages, which is why both FROM alpine:${VERSION} lines correctly resolve to alpine:3.18. However, a global ARG is not automatically available inside any build stage for use in instructions like RUN, COPY, or ENV — it must be re-declared with a bare ARG VERSION inside that stage. The first stage does this (ARG VERSION on line 3), so VERSION is usable there. The second stage does not re-declare it, so when Docker performs variable substitution on the RUN echo "Running on Alpine ${VERSION}" line, VERSION is undefined for that stage and is replaced with an empty string. The resulting file content is therefore Running on Alpine (with a trailing space and no version number). The build does not fail — undefined ARG substitutions silently resolve to empty strings, not errors.

You are using Docker BuildKit and have the following instruction in a Dockerfile:#

Options

Show answer

A BuildKit RUN --mount=type=cache mount persists across builds on the same builder but is never committed into any image layer. This means cached dependencies (like Maven's .m2 repository) speed up rebuilds without bloating the final image or being visible at runtime. The cache is a build-time-only construct — no VOLUME instruction is needed, and the contents are not discarded after each build.

Why:

BuildKit's RUN --mount=type=cache creates a persistent cache directory on the builder that survives across builds, speeding up subsequent builds by reusing downloaded dependencies or compiled artifacts. Critically, a cache mount is a build-time-only construct: its contents are NOT committed into any image layer. This means the final image stays small (no leftover .m2 cache) and runtime containers do not see the cached files. The claim that the cache mount contents are committed into the resulting image layer is wrong because cache mount contents are never written to a layer. The claim that the Dockerfile must also declare a matching VOLUME /root/.m2 instruction for the cache mount to function correctly is wrong because no VOLUME declaration is needed — --mount=type=cache is self-contained. The claim that the cache mount is discarded immediately after each build completes is wrong because the cache explicitly persists across builds, which is its entire purpose.

Given the Dockerfile below (built with BuildKit or the classic builder; the base image is Alpine 3.19, whose shell is BusyBox sh), when a container is built from it and run, what single word is printed to stdout?#

FROM alpine:3.19
ENV MESSAGE=hello
RUN export MESSAGE=world
RUN echo $MESSAGE > /tmp/out.txt
CMD ["cat", "/tmp/out.txt"]
Show answer
hello
Why:

Each RUN instruction runs in its own new shell process, so the export MESSAGE=world in the second RUN only changes that shell's environment and does not persist to later RUN instructions. ENV values are stored in the image configuration and are present for every subsequent RUN, so the third RUN's echo $MESSAGE expands to hello, which is written to /tmp/out.txt. The CMD then runs cat /tmp/out.txt at container start, printing hello.

Given the following Dockerfile, what does the final image contain and what base image does it use? Explain the key characteristics of the resulting image.#

Show answer

The final image is built FROM scratch, which is Docker's empty base image containing no operating system, shell, or package manager. It contains only the statically-linked Go binary /server copied from the builder stage, because CGO_ENABLED=0 ensures the binary has no external C library dependencies. The ENTRYPOINT is set to run that binary when the container starts.

Why:

The Dockerfile is a multi-stage build. The first stage (labeled builder) starts from golang:1.21 and compiles the Go program with CGO_ENABLED=0 and GOOS=linux, which yields a statically-linked binary with no dependence on external C libraries. The final stage uses FROM scratch, Docker's empty base image that contains no files, operating system, shell, or package manager. COPY --from=builder copies only the compiled /server binary into that stage, and ENTRYPOINT makes /server the process that runs at container start. The result is a very small image whose only content is one static binary, which also leaves a minimal attack surface.

Consider the following Dockerfile:#

Options

Show answer

The output is Image: with an empty value after the colon and space. An ARG declared before the first FROM is a global build argument usable for FROM interpolation, but it is not available inside the build stage unless redeclared with an ARG instruction after the FROM. Since BASE_IMAGE is not redeclared, the shell expansion of $BASE_IMAGE in the RUN command is empty. The build succeeds — the variable is simply unset in the shell environment, not an error.

Why:

An ARG declared before the first FROM is a global build argument. It is available for interpolation in FROM lines (so the base image correctly resolves to python:3.12-slim), but it is not available inside any build stage unless it is redeclared with an ARG instruction after the FROM. Because ARG BASE_IMAGE is not redeclared within the stage, the shell variable $BASE_IMAGE in the RUN command is unset and expands to an empty string. The build therefore succeeds (there is no error — an unset shell variable simply expands to nothing), and /tmp/info.txt contains Image: with a trailing empty value.

In a Dockerfile built with BuildKit (# syntax=docker/dockerfile:1, so --mount is available), consider this multi-stage build:#

Options

Show answer

A BuildKit type=cache mount's contents are never saved into an image layer, while files a RUN instruction writes outside the mount, such as the Go binary at /app/bin/myapp, are committed to that stage's layer and can be copied by a later stage.

Why:

BuildKit cache mounts are persistent cache storage managed by BuildKit that is overlaid on the target path only for the duration of that single RUN instruction; their contents are not written into any image layer, so they never appear in docker history. Filesystem changes made by the RUN outside the mount path (here, the binary at /app/bin/myapp) are captured in the builder stage's layer as usual, which is why COPY --from=builder can retrieve it. The option claiming the cache is a committed layer is wrong for the reason above. The claim that the cache is wiped every build is wrong because the mount persists across builds (it may be pruned by cache management, but that is not a per-build wipe). The claim that it is a named volume is wrong because cache mounts are internal BuildKit cache entries, not volumes listed by docker volume ls.

Related interview questions

Job market

See docker salaries and hiring demand from live job postings.

Practise these until they stick

That's every question we hold on this topic, and the page marks what you pick. What it can't do is remember. A free account keeps every answer, and what you miss comes back until it's right: after a day, then at longer gaps.

Start with this topic

Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan

Interview booked? Practise the job ad instead

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.