Docker interview questions
Docker and container interview questions from fresher to senior — images and layers, multi-stage builds, runtime and networking, and container security and isolation, tagged by experience level.
Fresher 0–1y · Junior 1–3y · Mid 3–6y · Senior 6+y — every answer is tagged so you can prep for your level.
Image vs container?Fresher
An image is an immutable, layered template; a container is a running (or stopped) writable instance of that image with its own process, network, and a thin read-write filesystem layer on top.
docker images # templates on disk docker ps -a # containers (running + stopped) docker run -d nginx:1.27 # start a container from an image
What is a layer?Fresher
Each Dockerfile instruction that changes the filesystem creates a cached, content-addressed layer. Layers are shared across images and rebuilt only when their instruction or inputs change — which is why instruction order matters.
docker history nginx:1.27 # each line = one instruction = one cached layer
CMD vs ENTRYPOINT?Fresher
ENTRYPOINT sets the fixed executable; CMD provides default arguments (or the whole command if there is no ENTRYPOINT). Use ENTRYPOINT for the binary and CMD for overridable defaults; prefer exec form.
ENTRYPOINT ["python", "app.py"] # fixed binary CMD ["--port", "8080"] # default, overridable # docker run img --port 9090 -> python app.py --port 9090
COPY vs ADD?Fresher
Both copy files in; ADD also auto-extracts local tarballs and can fetch URLs. Prefer COPY for predictability and reserve ADD for when you specifically want tar extraction.
How do you build an image and run it?Fresher
docker build reads the Dockerfile and produces a tagged image; docker run starts a container from it. Publish ports with -p and pass config with -e.
docker build -t app:1 . docker run -d -p 8080:80 -e LOG_LEVEL=info app:1 docker logs -f app
What does a container share with the host?Junior
The host kernel — containers are isolated processes using namespaces and cgroups, not virtual machines. That is why a container must match the host kernel/arch and why kernel-level isolation matters for security.
What happens when you run `docker run`?Mid
The CLI calls the daemon, which pulls the image if missing, creates a writable layer, sets up namespaces and cgroups, and starts your process as PID 1 inside them.
There is no VM boot — the container is just your process in isolated namespaces (PID, net, mount …) with cgroup limits, which is why start-up is milliseconds. The process runs as PID 1, so it must handle signals itself or you add --init to get a reaper.
How do you shrink an image?Junior
Use a small base (alpine/distroless), a multi-stage build to leave build tools behind, combine and clean up in single RUN layers, and a .dockerignore to keep the build context small.
FROM gcr.io/distroless/static # tiny, no shell COPY --from=build /app /app # + .dockerignore keeps the build context small
What is a multi-stage build?Junior
A Dockerfile with multiple FROM stages where a later stage COPY --from an earlier one takes only the built artifact, so compilers, dev headers, and caches never ship in the final image.
FROM golang:1.22 AS build WORKDIR /src COPY . . RUN CGO_ENABLED=0 go build -o /app ./cmd FROM gcr.io/distroless/static COPY --from=build /app /app ENTRYPOINT ["/app"]
How does build cache invalidation work?Mid
Layers cache top-down; once an instruction changes, every layer after it rebuilds. Order stable steps first and copy dependency manifests (package.json, go.mod) before source so dependency installs stay cached.
Every instruction is a cache key; change one and everything after it rebuilds. The classic win is copying the dependency manifest and installing deps before copying source, so an app-code change does not re-run a slow dependency install.
COPY package.json package-lock.json ./ RUN npm ci # cached until deps change COPY . . # source changes here RUN npm run build
What does BuildKit add over the legacy builder?Mid
Parallel stage execution, better caching (including remote cache export/import), build secrets and SSH mounts that never land in layers, and cache mounts for package managers — enabled by default in modern Docker.
BuildKit builds independent stages in parallel and can import/export a remote cache for CI. --mount=type=cache gives a persistent package cache; --mount=type=secret exposes a secret only during that RUN so it never lands in a layer.
# syntax=docker/dockerfile:1 RUN --mount=type=cache,target=/root/.cache/pip pip install -r req.txt # --mount=type=secret,id=pypi keeps a token out of the image
Why choose distroless or scratch?Mid
They remove the shell and package manager, shrinking the image and the attack surface (no busybox, no apt). The trade-off is harder debugging — you attach an ephemeral debug container instead of exec-ing a shell.
No shell and no package manager means far fewer CVEs and nothing for an attacker to pivot with, but you cannot exec a shell to debug — use `docker debug` or an ephemeral sidecar sharing the PID namespace.
FROM gcr.io/distroless/base-debian12 COPY --from=build /app /app USER nonroot ENTRYPOINT ["/app"]
How do you inspect an image’s size and layers?Mid
docker history shows per-layer size and the instruction that created it; docker image inspect shows metadata; the dive tool visualizes wasted space and duplicated files.
docker history --no-trunc app:1
docker image inspect app:1 --format '{{.Size}}'
dive app:1 # interactive layer explorerTags vs digests — how do you pin an image?Mid
Tags are mutable (nginx:1.27 can change under you); a digest (image@sha256:…) is immutable content-addressing. Pin by digest in production so a deploy is reproducible and cannot be silently swapped.
docker inspect --format '{{index .RepoDigests 0}}' nginx:1.27
# FROM nginx@sha256:... <- immutable, reproducibleBind mount vs named volume?Junior
A bind mount maps a host path directly (great for dev); a named volume is Docker-managed storage that survives container removal and is the right choice for databases and production data.
docker run -v $PWD:/app app:1 # bind mount (dev) docker volume create pgdata docker run -v pgdata:/var/lib/postgresql/data postgres:16
What are the Docker network drivers?Mid
bridge is the default single-host network; host shares the host network namespace (no isolation, no port mapping); none disables networking; overlay spans hosts in Swarm. A custom bridge network adds name-based DNS.
docker network create appnet docker run -d --name db --network appnet postgres:16 docker run --network appnet app:1 # reaches "db" by name
How do containers talk to each other?Mid
On a user-defined bridge network they resolve each other by container/service name via Docker’s embedded DNS. The default bridge does not provide name resolution, so always create a user-defined network.
On a user-defined bridge, Docker runs an embedded DNS at 127.0.0.11 that resolves container names to their current IPs, so you never hardcode addresses. The legacy default bridge lacks this — another reason to always create your own network.
How does publishing a port work?Junior
docker run -p 8080:80 tells the daemon to add an iptables DNAT rule mapping host:8080 to the container IP:80. Without -p a container is reachable only from other containers on its network.
docker run -d -p 8080:80 nginx:1.27 docker port <container>
What does a HEALTHCHECK do?Mid
It defines a command Docker runs periodically to mark the container healthy/unhealthy; orchestrators and `depends_on: condition: service_healthy` can gate on it. It reflects app readiness, not just process liveness.
HEALTHCHECK --interval=10s --timeout=2s CMD curl -f localhost/health || exit 1
# compose: depends_on: { db: { condition: service_healthy } }How do you limit a container’s resources?Mid
--memory and --cpus (or compose deploy.resources) map to cgroup limits; without them a container can starve the host. Set both, and pair memory limits with an app aware of them (e.g. JVM/Node heap flags).
docker run --memory=256m --cpus=0.5 app:1 docker stats # live per-container usage
Why not run as root in a container?Junior
Container root maps toward host root; a kernel escape or a mounted docker socket becomes far more dangerous. Add a USER, drop capabilities, and run read-only where possible.
# in the Dockerfile RUN adduser -D app USER app # or at runtime docker run --user 10001:10001 --read-only app:1
What are Linux capabilities and how do you use them with containers?Mid
Capabilities split root’s powers into units (NET_BIND_SERVICE, SYS_ADMIN, …). Containers should drop ALL and add back only what is required, rather than run fully privileged.
A container starts with a default capability set, but most apps need none. Drop ALL and add back only what is required — e.g. NET_BIND_SERVICE to bind port 80 as non-root — rather than --privileged, which grants everything and disables most isolation.
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE app:1
How do namespaces and cgroups provide isolation?Senior
Namespaces virtualize what a process can see (PID, net, mount, user, UTS, IPC); cgroups limit what it can use (CPU, memory, I/O). Together they are the container — not a security sandbox as strong as a VM.
Namespaces control what a process sees; cgroups control what it consumes; the user namespace is what makes rootless possible — root inside the container maps to an unprivileged UID outside. None of it is as strong as a VM boundary, which is why sensitive workloads add gVisor/Kata.
What is rootless Docker and why does it matter?Senior
The daemon and containers run as an unprivileged user via user namespaces, so a breakout lands as a normal user, not host root. It closes the biggest single risk of the classic root daemon, with some networking/storage caveats.
Because container root maps to your unprivileged host UID, an escape yields a normal user rather than host root — removing the single biggest risk of the classic daemon. The cost is some networking, storage-driver, and low-port limitations to work around.
What are common container escape vectors?Senior
Mounting the docker socket, --privileged, host PID/network namespaces, writable hostPath mounts, and dangerous capabilities like SYS_ADMIN. Defense: drop caps, no privileged, read-only rootfs, seccomp/AppArmor, and runtime detection.
The recurring culprits are a mounted docker.sock (equivalent to host root), --privileged, host PID/network namespaces, writable hostPath mounts, and caps like SYS_ADMIN. Defense in depth: no socket, no privileged, read-only rootfs, dropped caps, seccomp/AppArmor, and runtime detection.
# DANGER — each is a likely host takeover docker run -v /var/run/docker.sock:/var/run/docker.sock ... docker run --privileged ... docker run --pid=host --network=host ...
How do you scan and sign a container image?Senior
Scan with Trivy or Grype for CVEs in OS and app packages (fail the build on fixable criticals), and sign with cosign so consumers can verify provenance before running the image.
trivy image --severity HIGH,CRITICAL --exit-code 1 app:1 cosign sign app:1 cosign verify app:1
How do seccomp and AppArmor harden a container?Senior
seccomp filters which syscalls the container may make (Docker ships a default that blocks dozens of dangerous ones); AppArmor or SELinux add mandatory access control over files and capabilities. Together they shrink what a compromised process can do.
docker run --security-opt seccomp=profile.json app:1 docker run --security-opt apparmor=docker-default app:1