Highlights
- What this covers: around 30 real Docker interview questions, from fundamentals to live troubleshooting.
- Format: each answer is what a strong candidate actually says, plus what the interviewer is really testing.
- Who it is for: junior to senior developers, DevOps, and platform engineers.
- Real output: every command was run on current Docker (Engine 29.x); the sizes, exit codes, and DNS results are copied, not invented.
- The headline numbers: a multi-stage build took the same app from 1.27GB to 15.5MB, and an over-limit container died with exit code 137. Both are below, reproduced.
- How to use it: understand the model, do not memorize the commands. The follow-up question is where memorizers get caught.
You can write a Dockerfile in your sleep. You have shipped containers to production, debugged a docker compose stack at midnight, and you know the commands cold. Then the interviewer asks why your image is 1.2GB when the app is a 10MB binary, and you realize you have never actually had to explain the thing you do every day.
That is the gap Docker interviews are built to find. Not whether you can run a container, but whether you understand what a container is: a process with namespaces and cgroups around it, sharing the host kernel, built from stacked read-only layers. The questions below are the ones that actually come up, from the fundamentals a junior must nail to the troubleshooting scenarios that separate senior engineers. Every answer is written the way you would say it in the room, and every command and number here came from running it on real Docker, not from memory.
How Docker Interviews Actually Work
Docker rarely gets its own dedicated interview. It shows up inside a DevOps, backend, or platform round, usually in three shapes.
Quick fundamentals. Image versus container, what a layer is, what docker run does. One or two sentences each. These confirm you actually understand the tool rather than copy-pasting commands.
"Why" questions. Why is your image so big, why does latest bite teams, why run as non-root. This is where senior signal lives, because the answer is about trade-offs, not syntax.
Live troubleshooting. A container that exits instantly, two containers that cannot talk, code changes that will not show up after a rebuild. You debug out loud, and the method matters more than the lucky fix.
One framing worth carrying in: almost every hard Docker question reduces to one of two ideas. Either it is about layers (the build, the cache, image size) or about isolation (namespaces, cgroups, the shared kernel). Tie your answer back to the right one and you will sound like someone who understands the engine, not just the CLI. If the basics still feel shaky, Docker and containers for beginners is a solid reset.
Fundamentals
Q1. What is the difference between an image and a container?
An image is a read-only template: the filesystem and metadata your app needs, frozen. A container is a running (or stopped) instance of that image, with a thin writable layer on top. The relationship is the one between a class and an object, or a program on disk and a process. One image spawns many containers, and each container's changes live in its own writable layer, not in the image.
What they're really testing: whether you understand that the image is immutable and the container's changes are ephemeral. That single idea explains Q20 (data loss) and Q30 later.
Q2. How is a container different from a virtual machine?
A VM virtualizes hardware: each VM runs its own full guest kernel on top of a hypervisor. A container virtualizes the operating system: it is just a process on the host, isolated with namespaces (what it can see) and cgroups (what it can use), sharing the host kernel. That is why a container starts in milliseconds and ships in megabytes while a VM takes seconds and gigabytes. The trade-off is the honest part of the answer: because containers share the kernel, the isolation boundary is weaker than a VM's, which matters when you run untrusted code.
Q3. What is a Dockerfile?
A text file of instructions for building an image, read top to bottom. Each instruction (FROM, RUN, COPY, CMD, and so on) describes one step, and most of them create a new layer. FROM sets the base image, RUN executes a command at build time, COPY brings files in, and CMD or ENTRYPOINT define what runs when the container starts. The mental model to convey: a Dockerfile is a repeatable recipe, which is the whole point, because anyone can rebuild the identical image from it.
Q4. What are image layers, and how does the build cache use them?
Each instruction in a Dockerfile produces a layer, and layers stack into the final image. You can see them:
$ docker history demo:cache
CREATED BY SIZE
CMD ["python" "app.py"] 0B
COPY app.py . # buildkit 12.3kB
RUN pip install --no-cache-dir -r requirements 13.1MB
COPY requirements.txt . # buildkit 12.3kB
WORKDIR /app 8.19kB
The cache works per layer: if an instruction and its inputs have not changed, Docker reuses the cached layer instead of rebuilding it. Watch a rebuild after changing only app.py:
$ docker build -t demo:cache .
=> CACHED [2/5] WORKDIR /app
=> CACHED [3/5] COPY requirements.txt .
=> CACHED [4/5] RUN pip install --no-cache-dir -r requirements.txt
=> [5/5] COPY app.py .
The dependency install is CACHED because requirements.txt did not change, so only the COPY app.py step and everything after it re-run. This is the single most useful Docker concept for interviews, and it powers Q19 and Q28. If layers are still fuzzy, our breakdown of Docker image layers walks through them properly.
Q5. What is the difference between COPY and ADD?
COPY copies local files and directories into the image, and that is all it does. ADD does the same but with two extra behaviors: it can fetch a remote URL, and it auto-extracts a local tar archive. The senior answer is the recommendation: prefer COPY, because ADD's magic is surprising and the auto-extract has bitten people. Reach for ADD only when you specifically want to unpack a tarball.
Q6. What actually happens when you run docker run?
Docker checks for the image locally and pulls it from the registry if it is missing, creates a container (a writable layer plus the config) from that image, and then starts it by running the ENTRYPOINT/CMD process as PID 1 inside its namespaces. So docker run is really three steps, pull, create, start, which is why people who know that can explain where it can fail: a pull can fail on auth, a start can fail on a bad command.
Q7. How do you list and clean up containers and images?
docker ps shows running containers, docker ps -a includes stopped ones, and docker images lists images. For cleanup, docker system prune removes stopped containers, unused networks, and dangling images, while docker system prune -a also removes any image not used by a container. The detail that signals experience: stopped containers and old image layers silently eat disk, so knowing prune exists (and that -a is the aggressive version) is the practical answer.
Q8. What is the difference between docker stop and docker kill?
docker stop sends SIGTERM and gives the process a grace period (10 seconds by default) to shut down cleanly, then sends SIGKILL if it is still alive. docker kill sends SIGKILL immediately, no grace. So stop is the polite shutdown and kill is the hard one. This connects directly to Q23: if your app ignores SIGTERM or never receives it, docker stop always feels like it hangs for ten seconds.
Intermediate
Q9. How does data persist in Docker, and what are the options?
By default it does not: a container's writes go to its writable layer, which is deleted with the container. To persist data you use volumes (managed by Docker, the recommended default), bind mounts (a host path mounted in, great for local dev), or tmpfs (in-memory, for secrets you do not want on disk). A volume outlives the container:
$ docker run --rm -v demodata:/data busybox sh -c 'echo "survives the container" > /data/file.txt'
# container is gone; a brand new one reads the same volume:
$ docker run --rm -v demodata:/data busybox cat /data/file.txt
survives the container
What they're really testing: whether you know that container storage is ephemeral by default. Our guide to Docker storage and volumes covers the drivers behind this.
Q10. How does Docker networking work out of the box?
Docker installs three networks: bridge (the default, a private internal network where each container gets an IP), host (the container shares the host's network stack with no isolation), and none (no networking). Unless you say otherwise, a container joins the default bridge. The thing worth knowing for the next question: not all bridges behave the same.
Q11. Two containers cannot reach each other by name. What is going on?
Almost always they are on the default bridge, which has no built-in DNS, so container names do not resolve. Put them on a user-defined network and Docker's embedded DNS resolves names automatically. The contrast is stark when you run it:
# user-defined network: name resolves
$ docker run --rm --network demonet busybox nslookup web
Name: web
Address: 172.19.0.2
# default bridge: same lookup fails
$ docker run --rm busybox nslookup web2
** server can't find web2: NXDOMAIN
The fix in one sentence: create a user-defined network (or let Compose do it for you) and the service names just work. This is one of the most common "it works on my machine" networking gotchas, and our walkthrough of networking Docker containers goes deeper.
Q12. What is the difference between EXPOSE and publishing a port with -p?
EXPOSE is documentation: it records which port the container listens on, but it does not open anything. -p 8080:80 (or --publish) actually maps a host port to a container port so traffic from outside can reach it. Candidates conflate them constantly, so stating plainly that EXPOSE alone makes nothing reachable is a clean signal you have actually published a port in anger.
Q13. What is Docker Compose, and when do you reach for it?
Compose defines a multi-container application in a single YAML file: services, networks, and volumes declared once, brought up with docker compose up. You reach for it when an app is more than one container (an API plus a database plus a cache) or when you want a reproducible local environment. A bonus that ties back to Q11: Compose puts your services on a user-defined network by default, so they can resolve each other by service name with no extra work.
Q14. Why is the latest tag considered risky?
Because latest is not "the newest version", it is just the default tag Docker applies when you do not specify one. It does not auto-update, and worse, it is a moving target: two docker pull commands a month apart can give you different images, so a build that worked yesterday can break today with no change on your side. The senior answer is the practice: pin explicit version tags (or better, digests) in anything that matters, and treat latest as fine only for throwaway local work.
Q15. How do you reduce the size of a Docker image?
Four levers, roughly in order of impact: use a multi-stage build so build tools do not ship in the final image (Q18), start from a slim or alpine base instead of a full OS, combine and order RUN steps to avoid extra layers and clean up caches in the same layer, and add a .dockerignore so you are not copying .git and node_modules into the build context. The proof is in Q18, where these take the same app from 1.27GB to 15.5MB.
Q16. What is the difference between RUN, CMD, and ENTRYPOINT?
RUN executes at build time and bakes its result into a layer (installing packages, for example). CMD and ENTRYPOINT define what runs at container start, not during the build. The distinction between those two: ENTRYPOINT sets the fixed executable, while CMD provides default arguments that are easy to override on the command line. A common pattern is ENTRYPOINT ["python"] with CMD ["app.py"], so docker run image other.py swaps the script but keeps the interpreter.
Q17. How do you pass configuration and secrets into a container?
Configuration goes in through environment variables (-e or an env_file) or mounted config files, so the same image runs in dev and prod with different settings. Secrets are the part interviewers care about: do not bake them into the image, because anyone who pulls it can read them out of the layers with docker history. Use runtime environment variables for low-sensitivity values, and a real secrets mechanism (Docker/Swarm secrets, or the orchestrator's secret store mounted as a file) for anything that matters. Saying "secrets never go in the Dockerfile" out loud is the point of the question.
Advanced
Q18. Explain multi-stage builds and why they matter.
A multi-stage build uses more than one FROM: an early stage with the full toolchain compiles your app, and a final slim stage copies in only the built artifact with COPY --from=builder. The build tools never ship. The effect is not subtle. Same Go app, two Dockerfiles:
$ docker images
REPOSITORY TAG SIZE
demo single 1.27GB
demo multi 15.5MB
The single-stage image carries the entire Go toolchain (over a gigabyte of compilers you never run in production); the multi-stage image carries Alpine plus a static binary. Smaller images mean faster pulls, a smaller attack surface, and less to scan. This is a favorite because the payoff is so concrete, and how to create Docker images shows the build side in detail.
Q19. How does instruction order affect the build cache?
The cache invalidates top-down: once one layer changes, every layer after it rebuilds, even if those instructions are identical. So you order a Dockerfile from least to most frequently changing. Copy your dependency manifest and install dependencies before copying your application code, because your code changes on every commit while your dependencies rarely do. Get the order wrong (copy all your source first, then install) and every one-line code change blows away the dependency cache and reinstalls everything. This is the practical reason Q4's CACHED lines matter.
Q20. Are containers really isolated, the way a VM is?
Not to the same degree, and a good candidate is honest about it. Containers are isolated by kernel features: namespaces give each container its own view of processes, network, mounts, and users, and cgroups limit how much CPU and memory it can consume. But every container on a host shares that host's kernel. So a kernel vulnerability or a misconfigured privileged container can cross the boundary in ways that are much harder against a VM's hypervisor. The summary that lands: containers isolate processes, VMs isolate kernels, and that difference drives your security choices.
Q21. Why should a container not run as root, and how do you avoid it?
By default a container process runs as root, and that root maps to real UID 0 on the host kernel. You can see the default:
$ docker run --rm busybox id
uid=0(root) gid=0(root) groups=0(root),10(wheel)
$ docker run --rm --user 1000:1000 busybox id
uid=1000 gid=1000 groups=1000
If an attacker escapes a root container, they are far closer to root on the host. The fix is to add a USER instruction in your Dockerfile (or run with --user) so the process runs unprivileged, and to grant only the specific capabilities it needs. "Run as non-root by default" is exactly the kind of practice senior interviewers listen for.
Q22. What is the difference between the exec form and the shell form of CMD?
The exec form, CMD ["python", "app.py"], runs the binary directly as PID 1. The shell form, CMD python app.py, wraps it as /bin/sh -c "python app.py", so a shell sits in front of your process. You can see that the exec form makes your command PID 1:
$ docker run --rm busybox ps -o pid,args
PID COMMAND
1 ps -o pid,args
Why it matters leads straight into the next question: when a shell wraps your app, signals from docker stop may hit the shell instead of your process. Prefer the exec form for anything long-running.
Q23. Why does docker stop sometimes take ten seconds, and how do you fix it?
Because the process is not receiving or handling SIGTERM, so Docker waits out the grace period and then SIGKILLs it. Two usual causes. First, the shell form (Q22): /bin/sh -c "app" can leave the shell as PID 1, and a bare shell does not forward SIGTERM to your app, so the signal never reaches it. (With a single command, many shells exec it so your app becomes PID 1 anyway, which is why this behavior feels inconsistent; multiple commands or a chain keep the shell in front.) Second, the app genuinely ignores SIGTERM. The fixes: use the exec form so your app is PID 1 and gets the signal directly, handle SIGTERM in your code for a graceful shutdown, and add an init process (docker run --init, or tini) to forward signals and reap zombie children. A candidate who connects "slow stop" to "PID 1 and signals" is showing real depth.
Q24. What is a distroless or minimal base image, and why use one?
A minimal base (Alpine, or a distroless image that contains only your app and its runtime, no shell or package manager) shrinks the image and, more importantly, shrinks the attack surface. No shell means an attacker who lands in the container has far fewer tools, and fewer packages means fewer CVEs for your scanner to flag. The trade-off worth naming: debugging is harder when there is no shell to exec into, so some teams keep a debug variant. It is the same instinct as running non-root, minimize what is in the container.
Scenario and Troubleshooting
These questions mirror real incidents, not trivia, and the best preparation is having actually fixed one. KodeKloud Engineer hands you exactly this kind of real Docker ticket on a live system, so you can walk in having already solved the thing they are asking about.
Q25. Your image is over a gigabyte and builds slowly. How do you diagnose and shrink it?
Start by finding the weight: docker history <image> shows the size of each layer, so you can see what is actually big (usually the base image and a dependency-install step). Then apply the levers from Q15, with multi-stage first. The numbers are not hypothetical:
$ docker images
demo single 1.27GB
demo multi 15.5MB
Moving build tooling into an earlier stage that never ships is what closes most of that gap. For slow builds specifically, also check your layer order (Q19) and your .dockerignore, because a bloated build context makes every build slower before anything even runs.
Q26. A container exits immediately after you start it. How do you debug it?
First read the logs and the exit code: docker ps -a shows the status, and docker logs <container> shows what it printed before dying.
$ docker ps -a
NAMES STATUS COMMAND
justexit Exited (0) 1 second ago "sh -c 'echo ...'"
The most common cause is not an error at all: the container's main process finished and exited, so the container stopped, exactly as designed. A container lives only as long as its PID 1 process. If you expected a long-running server, check that your CMD actually starts one in the foreground (not backgrounded, not a one-shot command). A non-zero exit code points instead at a crash or a bad command, and the logs will usually tell you which.
Q27. After a rebuild, your code changes still are not showing up. Why?
The build cache is serving you a stale layer. If your COPY of the source sits behind instructions that did not change, or you are running the old image because you did not retag, you get yesterday's code. Confirm the build actually re-ran the COPY of your source rather than printing CACHED, force it with docker build --no-cache if needed, and make sure you are running the image you just built. Nine times out of ten this is the cache doing exactly what Q19 describes, just when you did not want it to.
Q28. A container was killed with exit code 137. What happened?
It was sent SIGKILL, and 137 is 128 + 9 (signal 9 is SIGKILL). The usual culprit is the out-of-memory killer: the container hit its memory limit and the kernel killed it. You can confirm it directly:
$ docker run --memory=20m --memory-swap=20m busybox sh -c 'tail /dev/zero'; echo "exit: $?"
exit: 137
$ docker inspect <container> --format 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}'
OOMKilled=true ExitCode=137
OOMKilled=true is the smoking gun. The fix is to raise the memory limit if the workload genuinely needs it, or to fix the leak if it does not. This is the same 137 you meet on a plain Linux box, so it is worth recognizing on sight.
Q29. Data written inside a container disappeared after docker rm. Why, and how do you prevent it?
Because writes went to the container's writable layer, which is destroyed with the container (back to Q1 and Q9). Nothing was misconfigured; that is how containers work. To keep data, write it to a volume or a bind mount instead, so it lives outside the container lifecycle. The volume demo in Q9 is the prevention: a new container mounting the same volume still sees the file. The one-line rule: if you want it to survive, it does not belong in the container's filesystem.
Q30. A teammate says "it works in my container but fails in the registry version." Where do you look?
This is usually one of three things, and naming them in order shows method. First, tag drift: they built locally and never pushed, or the registry tag points at an older image, so confirm the digest, not just the tag. Second, a missing build artifact: something present in their local build context (an untracked file, an env var baked at build time) is not in the clean build. Third, latest ambiguity (Q14): different machines pulled different images under the same tag. The fix in every case is determinism, pin digests, keep the Dockerfile self-contained, and rebuild from a clean checkout.
Quick-Revision Cheat Sheet
The night before, scan this instead of rereading the guide.
Conclusion
Nearly every question here comes back to the two ideas from the start: layers and isolation. Image size, the build cache, and "why is my change not showing up" are all layers. Container versus VM, running as non-root, and exit 137 are all isolation and resource limits. When a question lands, reach for the underlying model first, and the specific command falls out of it.
In the last 48 hours before the interview, do not re-read all 30 answers. Build one multi-stage image and watch the size drop, break a container's network on purpose and fix it, and trigger an OOM kill so 137 means something to you. The Docker for the Absolute Beginner course gives you a browser environment to try all of this, and KodeKloud's free full Docker course is a solid end-to-end refresher:
The answers you have actually run are the ones you will give without hesitating.
Ready to Stop Reciting and Start Doing?
Reading Docker answers is one thing. Shrinking a real image with a multi-stage build, debugging two containers that cannot resolve each other, and reading an OOMKilled=true off a dead container are different skills, and they only come from doing the work. The Docker for the Absolute Beginner course on KodeKloud drops you into a live environment with auto-validated tasks, so these become reflexes instead of flashcards. If you want the gentle on-ramp first, start with Docker and containers for beginners.
Create your free KodeKloud account ->
FAQs
Q1: Do I need to memorize every Docker flag?
No, and trying to is the wrong preparation. Interviewers probe understanding, not flag recall. Knowing that docker run is pull-create-start, or why multi-stage builds shrink an image, beats reciting every option of docker build. You can always check docker <command> --help in real work.
Q2: How deep does the Dockerfile knowledge need to go for a junior role?
For junior roles, the fundamentals carry most of the weight: image versus container, what a layer is, the common instructions, and basic run/ps/logs/build. Multi-stage builds and signal handling are usually nice-to-have rather than required, but understanding why images get big will set you apart even at the junior level.
Q3: Will they ask about Kubernetes too?
Often, yes, because Docker rarely lives alone now. Expect at least a bridge question like "how does this scale to many containers across machines", which is the opening for orchestration. Be ready to say where Docker stops and an orchestrator begins, even if the deep Kubernetes questions come in a separate round.
Q4: Are these enough to walk in prepared?
They cover the ground that comes up most, but the best preparation is hands-on. Run the commands in this guide, break things on purpose, and read the output. An interviewer can tell within one follow-up whether you have actually used Docker or just read about it, so make sure the answer is the former.
Sources: Docker Image Layers; How to Create Docker Images; Networking Docker Containers; Docker Engine Storage and Volumes; Docker for Beginners. All commands executed on Docker Engine 29.x (client and server 29.2.0, API 1.53) with golang:1.23, python:3.12-slim, alpine:3.20, nginx:alpine, and busybox.
Discussion