All resources
Docker · Interview prep

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.

ExperienceAll levelsFresherJuniorMidSenior

Fresher 0–1y · Junior 1–3y · Mid 3–6y · Senior 6+y — every answer is tagged so you can prep for your level.

Core concepts
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.

List images and containers
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.

See the layers
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 + CMD
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.

Build then run
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.

docker CLIdockerdpull imagewritable layernamespaces + cgroupsstart PID 1
Images & builds
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.

The levers that matter
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.

build stage (compile)COPY --from=buildslim final image
Go binary in ~10MB
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.

FROM base (cached)COPY deps + install (cached)COPY source (changed)rebuild from here down
Cache-friendly order
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.

Cache + secret mounts
# 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.

Distroless final stage
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.

Find the fat
docker history --no-trunc app:1
docker image inspect app:1 --format '{{.Size}}'
dive app:1   # interactive layer explorer
Tags 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.

Pin by digest
docker inspect --format '{{index .RepoDigests 0}}' nginx:1.27
# FROM nginx@sha256:...   <- immutable, reproducible
Runtime & networking
Bind 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.

Dev bind vs prod volume
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.

Create + attach a network
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.

appnet (user bridge)embedded DNS 127.0.0.11name resolves to current IP
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.

client to host:8080iptables DNATcontainer:80
Publish a port
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 + gated dependency
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).

Cap CPU + memory
docker run --memory=256m --cpus=0.5 app:1
docker stats   # live per-container usage
Security & isolation
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.

Run as non-root
# 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.

Drop all, add one
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.

processnamespaces (what it sees)cgroups (what it uses)isolated container
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.

Flags to never use casually
# 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.

Scan + sign in CI
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.

Apply profiles
docker run --security-opt seccomp=profile.json app:1
docker run --security-opt apparmor=docker-default app:1
Go deeper
Full, hands-on DevSecOps courses
Browse courses