Docker interview questions and answers

28 Docker questions from real interviews, sorted by seniority: images and layers, networking, volumes, Compose, security and the production failures you get asked to debug out loud.

28 questions7 junior12 mid9 seniorLive exerciseWhat each level testsHow to prepare

All 28 questions

  1. 1

    What is the difference between an image and a container?

    ImageContainer
    What it isA read-only templateA running instance of one
    FilesystemStacked layers, immutableThose layers plus a thin writable layer
    LifecycleBuilt once, reusedCreated, started, stopped, removed
    AnalogyA classAn object

    One image can back a hundred containers, and each gets its own writable layer, so writes inside one are invisible to the others.

    The consequence worth stating out loud: anything written inside a container is lost on docker rm unless it was written to a volume or a bind mount. That is what interviewers are checking you understand.

    link
  2. 2

    How is a container different from a virtual machine?

    Container and virtual machine stacks comparedTwo stacks side by side. The container stack is host hardware, host operating system, container engine, then apps sharing the host kernel. The virtual machine stack is host hardware, hypervisor, then each VM carrying its own full guest operating system before its app.ContainersVirtual machinesApp A · App B · App CContainer engineHost OS and kernel (shared)Host hardwareApp A · App BGuest OS · Guest OSHypervisorHost hardwareone kernel, milliseconds to starta kernel each, tens of seconds
    The guest operating system is the whole difference. A container is a process using the host's kernel, which is why it starts in milliseconds; a VM boots its own kernel, which is why its isolation boundary is stronger and its footprint is gigabytes.
    ContainerVirtual machine
    VirtualisesThe operating systemThe hardware
    KernelShares the host'sRuns its own guest kernel
    Start timeMillisecondsTens of seconds
    Image sizeMegabytesGigabytes
    Isolation boundaryThe kernelThe hypervisor

    The trade is that boundary. A kernel exploit crosses a container's; it does not cross a VM's. It is also why Linux containers need a Linux kernel, which is why Docker Desktop runs a Linux VM on macOS and Windows.

    link
  3. 3

    What happens when you run docker run nginx?

    Walk it through in order, because the point of the question is whether you know the steps are separable.

    1. 1The CLI sends the request to the daemon over /var/run/docker.sock.
    2. 2The daemon looks for nginx:latest locally. If it is missing it resolves the name to Docker Hub and pulls each missing layer.
    3. 3It creates the container: a new writable layer, plus namespaces for process, network, mount, IPC and UTS, and a cgroup for limits.
    4. 4It attaches the container to the default bridge network and gives it an IP.
    5. 5It starts the image's entrypoint and command as PID 1 inside the container.

    Knowing the daemon does the work, not the CLI, is what makes "Cannot connect to the Docker daemon" a five-second diagnosis later.

    link
  4. 4

    What is a Docker layer, and why does layer order matter?

    Each instruction in a Dockerfile that changes the filesystem creates a layer. Layers are content-addressed and cached, and a build reuses a cached layer only if that instruction and every instruction before it are unchanged.

    Image layers, with the container's writable layer on topFive stacked layers. From the bottom: the base image, a copy of package files, a dependency install, a copy of the source, and at the top the container's own writable layer. Layers below the writable one are read-only and shared between containers.Writable layerper container, lost on rmCOPY . .changes on every commitRUN npm cicached while package.json is unchangedCOPY package*.json ./changes rarelyFROM node:22base imagebuildordertopbase
    Layers are additive and read-only, so a file deleted by a later instruction is still carried in the earlier one. Ordering by rate of change is what makes the cache work: put what changes rarely at the bottom.

    That is why order matters. Put the parts that change rarely first and the parts that change on every commit last:

    COPY package*.json ./
    RUN npm ci
    COPY . .

    Copying package.json on its own means npm ci is only re-run when dependencies actually change. Reverse those and every one-character source edit reinstalls the whole dependency tree.

    link
  5. 5

    What is the difference between COPY and ADD in a Dockerfile?

    COPYADD
    Copies from the build contextYesYes
    Fetches a URLNoYes
    Auto-extracts a local tarNoYes
    RecommendedYesOnly when you need those extras

    Prefer COPY everywhere. Both extras are surprising in practice, and a .tar.gz you meant to copy verbatim getting silently unpacked is a genuinely confusing bug. Use COPY, and when you need a remote file use an explicit RUN curl so the fetch and its failure mode are visible.

    link
  6. 6

    How do you persist data from a container?

    With a volume or a bind mount, because the container's own writable layer is deleted with the container.

    A named volume is managed by Docker under /var/lib/docker/volumes. It is the right default for databases and application state: portable across hosts with the right driver, and not tied to a path on this machine.

    docker run -v pgdata:/var/lib/postgresql/data postgres:16

    A bind mount maps a specific host path into the container. It is right for development, where you want your source on the host and running in the container.

    docker run -v "$PWD":/app node:22

    A tmpfs mount lives in memory only and never touches disk, which suits secrets and scratch space.

    link
  7. 7

    What is a multi-stage Docker build, and what problem does it solve?

    A multi-stage build uses several FROM instructions in one Dockerfile and copies only the finished artefacts from the earlier stages into the last one. Only the final stage ships.

    FROM golang:1.23 AS build
    WORKDIR /src
    COPY . .
    RUN CGO_ENABLED=0 go build -o /app ./cmd/api
    
    FROM gcr.io/distroless/static
    COPY --from=build /app /app
    ENTRYPOINT ["/app"]

    It solves two things at once. Size: the compiler, module cache and source never reach the runtime image. Security: a distroless or scratch final image has no shell and no package manager, so there is very little to exploit and very little to appear in a CVE scan.

    The point worth making is that deleting build tools in a later RUN does not work. Layers are additive, so the bytes are still in the earlier layer and still in the image.

    link
  8. 8

    What is the difference between CMD and ENTRYPOINT in Docker?

    ENTRYPOINTCMD
    SetsThe executableIts default arguments
    Overridden by docker run argsNoYes
    Overridden by--entrypointAny trailing arguments

    Used together they give a sensible default that stays overridable:

    ENTRYPOINT ["ping"]
    CMD ["-c", "3", "localhost"]

    docker run img pings localhost three times. docker run img example.com pings example.com, because the CMD was replaced but the entrypoint was not.

    The second half of the answer is exec form versus shell form. ENTRYPOINT ["ping"] runs the binary directly as PID 1, so it receives SIGTERM and can shut down cleanly. ENTRYPOINT ping runs it under /bin/sh -c, the shell becomes PID 1, and it does not forward signals. That is why containers take exactly ten seconds to stop and are then killed: always use exec form.

    link
  9. 9

    Explain Docker's default network drivers and when you would use each.

    DriverWhat it doesUse it for
    bridgePrivate subnet on a virtual bridge, NAT to the outsideAlmost everything. A user-defined bridge also gives DNS, so containers resolve each other by name
    hostNo network isolation, the host's own stackWhen you genuinely need the host's stack. Linux only, and ports collide
    noneNo network at allJobs that should not have one
    overlaySpans multiple hostsSwarm and multi-host setups
    macvlanOwn MAC, appears as a device on the LANLegacy systems that require it

    The detail worth volunteering: the default bridge network has no embedded DNS, which is why every tutorial tells you to create your own.

    In practice: a user-defined bridge for almost everything, host when you genuinely need the host's stack.

    link
  10. 10

    Two containers are on the same host but cannot reach each other. How do you debug it?

    Establish whether they are on the same network first, because the most common cause is that they are not.

    docker inspect -f '{{json .NetworkSettings.Networks}}' api db

    If they are both on the default bridge, name resolution will not work: the default bridge has no embedded DNS. Create a user-defined network and attach both.

    If they share a user-defined network, check the name being used. Containers resolve each other by container name or network alias, not by image name and not by localhost. localhost inside a container is that container.

    Then check the process is listening on the right interface. An application bound to 127.0.0.1 inside the container is unreachable from outside it, and has to bind 0.0.0.0.

    docker exec api sh -c 'getent hosts db; nc -zv db 5432'
    link
  11. 11

    What is the difference between a Docker named volume and a bind mount?

    Ownership and lifecycle.

    A named volume is created and managed by Docker. It survives docker rm, is listed by docker volume ls, can use a driver to sit on NFS or a cloud disk, and when mounted into an empty directory Docker copies the image's existing contents into it on first use.

    A bind mount is just a host path. Docker does not manage it, does not know its lifecycle, and never copies image content into it. Mounting a bind mount over a populated directory hides what was there, which is the classic "my node_modules disappeared" problem when the host directory has none.

    Bind mounts are also a security consideration: mounting /var/run/docker.sock or / into a container is effectively handing over the host.

    link
  12. 12

    What does depends_on actually guarantee in Docker Compose?

    Only start order, not readiness. depends_on waits for the container to be *started*, not for the process inside it to be *ready*, so an application will happily start and fail to connect to a database that is still initialising.

    The fix is a condition tied to a healthcheck:

    services:
      db:
        image: postgres:16
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U postgres"]
          interval: 5s
          retries: 10
      api:
        depends_on:
          db:
            condition: service_healthy

    The better answer adds that applications should retry their own connections anyway. Dependencies restart in production, and nothing orchestrates start order there.

    link
  13. 13

    How do you make a Docker image smaller?

    In rough order of how much they gain:

    • Multi-stage builds. Ship only the artefact, never the toolchain.
    • A smaller base. node:22-alpine against node:22 is often 1.1 GB of difference. Distroless or scratch is smaller still where the binary is static.
    • Fewer, better layers. Chain related commands and clean up in the *same* RUN, because a later deletion does not shrink an earlier layer:
    RUN apt-get update \
     && apt-get install -y --no-install-recommends curl \
     && rm -rf /var/lib/apt/lists/*
    • A real `.dockerignore`. Excluding .git, node_modules and build output shrinks the context, speeds up the build, and avoids copying secrets in by accident.
    • Install production dependencies only, with npm ci --omit=dev or the equivalent.

    Worth naming the trade: Alpine uses musl rather than glibc, which occasionally breaks native modules and has caused real DNS and performance surprises.

    link
  14. 14

    A container exits immediately after starting. How do you find out why?

    Start with the exit code and the logs of the run that failed.

    docker ps -a --filter "name=api" --format '{{.Names}}\t{{.Status}}'
    docker logs api

    Read the exit code, because it names the class of fault:

    CodeMeansUsually
    0Finished normallyThere was no long-running process to begin with
    1Application errorConfiguration, or an unreachable dependency
    127Command not foundA wrong path, or no shell in a slim base image
    137SIGKILLThe out-of-memory killer
    139SegfaultA native crash, often an architecture mismatch
    143SIGTERMSomething asked it to stop

    If it dies too fast to inspect, override the entrypoint and look around:

    docker run --rm -it --entrypoint sh myimage

    The most common root cause is a container with nothing to keep it alive. A container runs exactly one foreground process and exits when that process exits.

    link
  15. 15

    Why should a container not run as root, and how do you avoid it?

    Because the root inside the container is, by default, the same UID 0 as root on the host. If an attacker escapes the container, or if a bind mount is writable, that UID is the one doing the damage.

    RUN adduser --system --uid 10001 app
    USER 10001

    Use a numeric UID rather than a name so Kubernetes can enforce runAsNonRoot, which cannot verify a username.

    A strong answer continues to the runtime flags, because the Dockerfile alone is not enough:

    docker run --read-only --cap-drop ALL --security-opt no-new-privileges myimage

    And it mentions user namespace remapping, which maps container root to an unprivileged host UID and is the real fix where you can enable it.

    link
  16. 16

    How do you get a secret into an image without leaking it?

    Not with ARG, and not with ENV. Both are stored in the image metadata and visible to anyone who runs docker history, even if a later layer deletes the file.

    At build time, use BuildKit secret mounts, which are available to one RUN and never written to a layer:

    RUN --mount=type=secret,id=npmtoken \
        NPM_TOKEN=$(cat /run/secrets/npmtoken) npm ci
    docker build --secret id=npmtoken,env=NPM_TOKEN .

    At runtime, inject secrets from outside the image: environment variables from a secret manager, mounted files, or the platform's own mechanism. The rule is that the image is a public artefact and should be safe to push to a registry anyone can read.

    link
  17. 17

    What is the problem with the Docker latest tag?

    latest is not a special pointer to the newest version. It is the default tag applied when you do not give one, and it moves whenever someone pushes to it.

    That creates three problems. Builds are not reproducible, because the same Dockerfile produces different images on different days. Rollbacks are unclear, because you cannot name the version you want to go back to. And caching gets confusing, since a node may already hold a stale latest and imagePullPolicy: IfNotPresent will use it.

    Tag with something immutable: a semantic version, a git SHA, or a build number. For the strongest guarantee, pin by digest, which is content-addressed and cannot move:

    myapp@sha256:9b2b...e41
    link
  18. 18

    How does the Docker build cache decide whether a layer can be reused?

    For most instructions the daemon hashes the instruction string plus the ID of the parent layer. If both match an existing layer, it is reused. Because the parent is part of the key, a cache miss invalidates every layer after it, which is the whole reason instruction order matters.

    COPY and ADD are the exception. They also hash the *contents* of the files being copied, so the cache breaks when a copied file changes even though the instruction text is identical. Note this includes file metadata in some cases, which is why a chmod in CI can break a cache that looked stable locally.

    A senior answer adds what this means in CI: a fresh runner has an empty cache, so you need --cache-from against a registry image, or BuildKit's --mount=type=cache for package manager directories, or the layers get rebuilt every time regardless of how well the Dockerfile is ordered.

    link
  19. 19

    Your image works locally on an M-series Mac and fails on the cluster with "exec format error". What happened?

    The image was built for linux/arm64 and the nodes are linux/amd64. The kernel cannot execute the binary, so it reports an exec format error, which reads like a corrupt file rather than an architecture mismatch.

    Confirm it in one command:

    docker image inspect myapp:1.0 --format '{{.Architecture}}/{{.Os}}'

    The fix is to publish a multi-architecture manifest rather than building for whatever the developer's laptop happens to be:

    docker buildx build --platform linux/amd64,linux/arm64 -t repo/app:1.4.2 --push .

    The detail that marks experience: in a mixed-architecture cluster the Pod schedules anywhere, so the same image works on some nodes and fails on others. It presents as a flaky deploy, not as a build problem.

    link
  20. 20

    Which Linux primitives actually provide container isolation?

    Three groups, and it is worth being precise because "containers are lightweight VMs" is the wrong mental model.

    Namespaces provide the isolation. pid gives the container its own process tree so its entrypoint is PID 1. mnt gives it its own filesystem view. net gives it its own interfaces and routing table. uts gives it its own hostname. ipc separates shared memory. user maps UIDs, so container root can be an unprivileged host user.

    cgroups provide the limits: CPU shares and quota, memory limits, block IO, and the PID count. A memory limit here is what produces an OOM kill with exit code 137.

    Capabilities, seccomp and LSMs reduce what the process may ask the kernel to do. Docker drops most capabilities by default and applies a seccomp profile blocking around forty syscalls.

    The conclusion an interviewer wants: it is all one shared kernel, so a kernel vulnerability crosses the boundary, and that is why untrusted multi-tenant workloads use gVisor, Kata or separate VMs.

    link
  21. 21

    Why does your container take ten seconds to stop, and how do you fix it?

    Because SIGTERM is not reaching the application. Docker sends SIGTERM, waits ten seconds, then sends SIGKILL. A container that always takes exactly ten seconds is being killed, not stopping.

    Almost always the cause is shell-form CMD. CMD npm start runs under /bin/sh -c, so the shell is PID 1 and does not forward signals to its child. Use exec form:

    CMD ["node", "server.js"]

    If you genuinely need a shell wrapper, exec the real process so it replaces the shell and inherits PID 1. Where the application spawns children, add an init such as --init or tini to reap zombies and forward signals.

    The second half is that the application has to handle SIGTERM: stop accepting new connections, finish in-flight requests, close pools, exit. Without that you drop requests on every deploy.

    link
  22. 22

    How do you handle logging for containers, and why not write to a file?

    Containers should write to stdout and stderr, and let the platform collect them. A log file inside a container disappears with the container, is invisible to docker logs, and fills the writable layer until the host runs out of disk.

    Docker's default json-file driver is worth knowing because it is unbounded by default. A chatty container will fill /var/lib/docker/containers and take the host down, which surfaces as "no space left on device" for everything else on the box.

    {
      "log-driver": "json-file",
      "log-opts": { "max-size": "10m", "max-file": "3" }
    }

    In a cluster, the node agent tails those files and ships them to a backend. The application should emit structured JSON lines with a correlation ID, since that is what makes them queryable once they arrive.

    link
  23. 23

    What is a healthcheck, and where does Docker's version not apply?

    HEALTHCHECK in a Dockerfile tells the Docker daemon how to test whether the container is working, and marks it healthy, unhealthy or starting.

    HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
      CMD curl -fsS http://localhost:8080/healthz || exit 1

    The important limitation: Kubernetes ignores it entirely. Kubernetes runs its own liveness, readiness and startup probes from the kubelet, and never reads the image's HEALTHCHECK. Claiming otherwise is a common way to lose credibility in an interview.

    It is also worth distinguishing the probe types, since conflating them causes real outages: readiness controls whether traffic is sent, liveness controls whether the container is restarted. A liveness probe pointed at a dependency will restart your whole fleet when that dependency has a bad minute.

    link
  24. 24

    How would you secure a container supply chain end to end?

    Cover the four stages rather than listing tools.

    Build. Pin base images by digest, not tag. Build from a clean checkout in CI, not from a laptop. Use multi-stage builds so the toolchain never ships, and BuildKit secret mounts so credentials never reach a layer.

    Scan. Run an image scanner such as Trivy or Grype in the pipeline and fail on fixable high and critical findings. Generate an SBOM so you can answer "are we affected?" without rebuilding everything. Scan continuously, since a clean image becomes vulnerable when a CVE is published, with no change on your side.

    Sign and verify. Sign images with Cosign, and enforce the signature at admission so the cluster refuses anything unsigned or from an unexpected registry.

    Run. Non-root with a numeric UID, read-only root filesystem, all capabilities dropped, no-new-privileges, a seccomp profile, and no /var/run/docker.sock mounted anywhere.

    The honest closing point is that most real incidents come from the boring end: a leaked registry credential or an over-permissive mount, not a novel kernel escape.

    link
  25. 25

    docker system df shows 40 GB reclaimable. Walk through cleaning it up safely.

    Look before deleting, and go from safest to least safe.

    docker system df -v

    Build cache first. It is almost always the largest item and nothing running depends on it:

    docker builder prune

    Then stopped containers and dangling images. Then images no container is using, which matters on a CI host that has pulled hundreds of tags:

    docker image prune -a

    Stop before volumes. docker volume prune deletes data, and "unused" only means no container currently references it, which is true of a database volume between deploys. Check docker volume ls and know what each one is first.

    Two things that catch people out: container logs are not counted as reclaimable by docker system df, so check /var/lib/docker/containers separately, and a full disk can also be exhausted inodes rather than bytes, which df -i will show and df -h will not.

    link
  26. 26

    Where does Docker Compose stop being the right tool?

    Compose describes containers on one host. It has no scheduler, so it cannot place work across machines, cannot reschedule when a host dies, and gives you no rolling update with health gating or automatic rollback. Scaling is manual and bounded by that one machine, and there is no built-in service discovery beyond the single Docker network.

    That makes it excellent for local development, integration tests in CI, and genuinely small single-host deployments where an hour of downtime is acceptable.

    Past that you want an orchestrator for scheduling, self-healing, rolling deploys, secret management and horizontal scaling. A good answer resists saying "always Kubernetes": Kubernetes has real operational cost, and ECS, Nomad or a managed platform are often the better trade for a small team.

    link
  27. 27

    How do you debug inside a container that has no shell?

    Distroless and scratch images have no shell on purpose, so docker exec -it sh fails. There are three options.

    Attach a debug container that shares the target's namespaces, which gives you your tools against their process tree and network:

    docker run -it --rm --pid=container:api --network=container:api nicolaka/netshoot

    In Kubernetes the same idea is a built-in:

    kubectl debug -it api-7d4f --image=nicolaka/netshoot --target=api

    Or inspect from outside: docker logs, docker inspect, docker diff to see what the container has written, and docker cp to pull a file out.

    Worth saying that you would not add a shell to the production image to make debugging easier. That removes the reason the image is hardened.

    link
  28. 28

    What does .dockerignore do, and why does it matter?

    It excludes paths from the build context, which is the set of files the CLI sends to the daemon before the build starts.

    It matters for three reasons. Speed: sending a 500 MB .git directory and local node_modules on every build is slow. Caching: a COPY . . invalidates its layer whenever any copied file changes, including files the build does not need. Security: without it, a .env file or a private key in the working directory can be copied straight into an image you then push to a registry.

    A reasonable starting point:

    .git
    node_modules
    npm-debug.log
    .env
    .env.*
    dist
    coverage
    Dockerfile
    .dockerignore
    link

Live exercise: a container will not stay up

This is the most commonly asked Docker debugging question, and it is asked as a conversation. Narrate this order and you will cover every cause without guessing.

  1. Is it exited, or is it restarting?

    docker ps -a --format '{{.Names}}\t{{.Status}}'
    yes
    Exited once means it ran and stopped, so read the exit code next.
    no
    Restarting repeatedly means it is crash-looping, so you need the logs of the run that failed, not the one starting now.
  2. Does the log show an application error?

    docker logs --tail 50 myapp
    yes
    The application itself failed. That is configuration, a missing environment variable, or an unreachable dependency at startup.
    no
    An empty log usually means the process was killed rather than that it failed, so stop reading logs and read the exit code.
  3. Is the exit code 137?

    docker inspect myapp --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
    yes
    The kernel killed it for exceeding the memory limit. Compare real usage against the limit, and check whether the runtime is sizing its heap from the host rather than the cgroup.
    no
    Exit 0 means the process completed, so there was no long-running process to begin with. 127 or 128 means the entrypoint path does not exist. 139 is a segfault.
  4. Does it die too fast to inspect?

    docker run --rm -it --entrypoint sh myimage
    yes
    Override the entrypoint and look around: check the binary exists, is executable, and is built for this architecture.
    no
    Exec in and check the thing the logs pointed at, usually environment variables or connectivity to a dependency.

What each level is testing

  1. 1

    Junior7 questions

    Whether the model is right. Image against container, why data disappears, what a layer is, and what docker run actually does. Definitions are fine here, as long as you can say the consequence: not just "images are read-only" but "so anything written in the container is gone unless it went to a volume".

  2. 2

    Mid12 questions

    Whether you have shipped something. Multi-stage builds, why CMD and ENTRYPOINT differ, how containers find each other, and how you debug one that will not start. Expect to be asked what you would change about a Dockerfile you are shown.

  3. 3

    Senior9 questions

    Judgement and trade-offs. How the build cache actually keys, signal handling and graceful shutdown, what the kernel primitives really provide, and where you would not use containers. The questions become open ones where naming the trade matters more than the answer.

What the round is like

A Docker round is usually thirty to forty minutes and moves in three stages. It opens with definitions to check you are not bluffing, moves to how you would build and ship an image, and ends with a failure you have to debug out loud. The last stage is where offers are decided, because anyone can recite what a layer is and far fewer can say why a container takes exactly ten seconds to stop.

How to prepare with this

  1. 1Filter to your level first and attempt each question out loud before opening the answer. Reading an answer you did not attempt feels like learning and is not.
  2. 2Mark a question known only when you can say it without the page. The button is there so the second pass is short.
  3. 3Do the senior questions even for a mid-level role. Interviewers commonly push one level past the job to find your edge, and the graceful-shutdown and build-cache answers are where most candidates stop.
  4. 4For anything you get wrong, go and break it. Run a container with a shell-form CMD and time docker stop. The ten seconds is the part you will remember.
  5. 5Prepare one real story about a container problem you fixed. Almost every round ends with some form of "tell me about a time", and a specific failure beats a general answer.

Learn the underlying material

Other question sets