QuizCluster
Cloud & DevOpsDevOps Engineer to Senior Platform / SRE15 min read

Docker Interview Guide: Images, Containers, Networking & Production Debugging

From Union Filesystems and cgroups to Multi-Stage Builds and Debugging OOMKilled Containers in Production

Priya Nair
Senior DevOps Engineer & Container Platform Lead
11+ Years Running Docker & Kubernetes Fleets in Production
Prep Timeline
3 to 5 Weeks
Format
System Design, Hands-on Container Debugging, Internals Deep-Dive
Conversion
+73% Infra Round Pass Rate
Docker Interview Guide: Images, Containers, Networking & Production Debugging
Executive Summary & Key Takeaways

What You Must Master to Clear This Track

  • Treat every Dockerfile instruction as a cache-keyed, immutable layer — instruction order determines whether a one-line code change forces a full dependency reinstall.
  • Containers are not lightweight VMs: isolation comes from Linux namespaces (pid, net, mnt, uts, ipc, user) and resource governance comes from cgroups, not a hypervisor.
  • Multi-stage builds should be the default for any compiled or transpiled language — ship the runtime, never the build toolchain.
  • Pick a network mode deliberately: user-defined bridge for single-host service discovery, host for latency-sensitive workloads, overlay for multi-host Swarm/K8s traffic.
  • OOMKilled (exit 137) and accumulating zombie processes are two of the most common production incidents — both have a specific, diagnosable root cause and a specific fix.
Structured Preparation Timeline

Step-by-Step Study Plan

Follow this sequential roadmap designed to take you from core foundations to advanced architecture and mock interviews.

Phase 1 (Weeks 1-2)

Union Filesystems, Build Cache & Multi-Stage Builds

Images, Layers & Dockerfile Craftsmanship

How OverlayFS merges read-only layers with a writable layer, how the build cache is keyed, and how multi-stage builds strip dev dependencies out of production images.

Key Milestones
  • Explain how each RUN/COPY/ADD instruction becomes an immutable, content-addressed layer.
  • Rewrite a bloated single-stage Dockerfile into a multi-stage build with a slim runtime base.
  • Reduce final image size using .dockerignore, minimal base images, and dependency-manifest-first COPY ordering.
Recommended Actions
  • Run `docker history <image>` and `docker image inspect` to see the exact size and command behind every layer.
  • Practice reordering Dockerfile instructions so cache invalidation only happens where source code actually changed.
  • Compare `node:18`, `node:18-slim`, and `node:18-alpine` image sizes and glibc/musl compatibility trade-offs.
Phase 2 (Weeks 3-4)

Namespaces, cgroups, Network Modes & Storage Drivers

Isolation Internals, Networking & Storage

The kernel primitives that make a container feel like an isolated machine, plus how to choose between bridge, host, and overlay networking and between volumes, bind mounts, and tmpfs.

Key Milestones
  • Trace the full `docker run` lifecycle: dockerd -> containerd -> containerd-shim -> runc -> namespaces + cgroups + pivot_root.
  • Compare default bridge, user-defined bridge, host, and overlay network drivers and when each is appropriate.
  • Explain overlay2 storage driver mechanics and why writes to the container layer trigger copy-on-write costs.
Recommended Actions
  • Inspect `/proc/<pid>/ns/*` for a running container to see its namespace file descriptors directly.
  • Stand up a user-defined bridge network and verify embedded DNS-based service discovery between two containers.
  • Read cgroup v2 files directly (`memory.max`, `memory.current`, `cpu.max`) rather than only trusting `docker stats`.
Phase 3 (Weeks 5-6)

OOMKilled Containers, Zombie Processes & Resource Limits

Production Debugging, Resource Governance & Orchestration Readiness

Diagnosing exit code 137, understanding why PID 1 must reap children, and setting memory/CPU limits that match real usage instead of guesses.

Key Milestones
  • Diagnose an OOMKilled container using `docker inspect`, `dmesg`, and cgroup memory accounting.
  • Explain why containers without an init process accumulate zombie/defunct processes and how `--init` fixes it.
  • Set memory and CPU limits from measured p95 usage instead of arbitrary defaults, and understand throttling vs killing.
Recommended Actions
  • Practice reading `docker inspect --format='{{.State.OOMKilled}}'` output during a mock incident.
  • Run a container with a process that forks children and orphans them; observe zombies with and without `--init`.
  • Build a checklist for 'container exits immediately' debugging: logs, exit code, entrypoint, foreground process requirement.
Deep-Dive Architecture & Concepts

1. Images, Layers & the Union Filesystem

A Docker image is not a single file — it is a stack of read-only layers merged at runtime by a union filesystem. Understanding how those layers are built, cached, and merged is the single highest-leverage topic in a Docker interview.

Union Filesystem (OverlayFS)

Each image layer is a read-only directory diff. OverlayFS stacks them as lowerdir entries and adds one writable upperdir for the running container, presenting a single merged view without copying every layer's contents.

Content-Addressed Build Cache

Every instruction produces a layer keyed by the hash of the previous layer plus the instruction and its inputs. If any earlier layer's cache key changes, every layer after it must rebuild, even if the later instructions themselves are unchanged.

Copy-on-Write for the Container Layer

A running container gets one thin writable layer on top of the image's read-only layers. The first write to any file triggers a full copy of that file up into the writable layer before the modification is applied.

Build Context & .dockerignore

The entire build context (everything in the directory passed to `docker build`) is sent to the daemon before the first instruction runs. An oversized context (node_modules, .git, build artifacts) slows every build and can leak files into cache keys unnecessarily.

Multi-Stage Build: Compile in a Fat Stage, Ship a Slim Runtime
dockerfile
# ---- Stage 1: install deps & compile ----
  FROM node:18 AS builder
  WORKDIR /app
  COPY package.json package-lock.json ./
  RUN npm ci
  COPY . .
  RUN npm run build
  
  # ---- Stage 2: slim production runtime ----
  FROM node:18-alpine AS runner
  WORKDIR /app
  ENV NODE_ENV=production
  COPY package.json package-lock.json ./
  RUN npm ci --omit=dev && npm cache clean --force
  COPY --from=builder /app/dist ./dist
  RUN addgroup -S app && adduser -S app -G app
  USER app
  EXPOSE 3000
  HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD wget -qO- http://localhost:3000/healthz || exit 1
  CMD ["node", "dist/server.js"]
Why it matters: Only the compiled dist/ output and production node_modules cross into the final stage — the TypeScript compiler, dev dependencies, and full source tree stay behind in the discarded builder stage, typically cutting image size by 4-6x.
Interviewer Insights & Pro Tips
  • Order Dockerfile instructions from least to most frequently changing: base image, OS packages, dependency manifests, `npm ci`/`pip install`, then application source code last.
  • Use `docker build --progress=plain` and `docker history <image>` to see exactly which layer is bloating the image before optimizing blindly.
Red Flags & Common Pitfalls
  • Copying the entire source tree (`COPY . .`) before installing dependencies invalidates the dependency-install cache layer on every single code change.
  • Forgetting a `.dockerignore` lets `.git`, `node_modules`, and local `.env` files bloat the build context and occasionally leak secrets into layers.
Deep-Dive Architecture & Concepts

2. Namespaces & cgroups: How Containers Achieve Isolation

A container is an ordinary Linux process with a restricted view of the system. That restricted view comes from two independent kernel mechanisms: namespaces (what the process can see) and cgroups (what the process can use).

PID Namespace

The container's first process becomes PID 1 inside its own PID namespace, unaware of any other process on the host. If that PID 1 exits, the entire namespace and every process in it is torn down.

Network Namespace

Each container gets its own network stack (interfaces, routing table, iptables rules). A veth pair connects the container's virtual interface to a bridge on the host, which is how bridge-mode networking is implemented under the hood.

Mount Namespace & pivot_root

The container sees its own private filesystem root, built by mounting the merged OverlayFS view and calling pivot_root so the host's real filesystem is no longer reachable from inside.

cgroups v2 (Unified Hierarchy)

Control groups enforce resource ceilings independent of namespaces: memory.max, cpu.max, and pids.max are written by the container runtime and enforced directly by the kernel scheduler and memory manager.

Container Runtime: From `docker run` to an Isolated Process

The full call chain from the Docker CLI down to a namespaced, cgroup-limited process on the host kernel.

1
CLI to dockerd
`docker run` sends a REST request over the Unix socket to the Docker daemon (dockerd).
2
dockerd to containerd
dockerd resolves the image from local layer storage and delegates container lifecycle management to containerd over gRPC.
3
containerd-shim to runc
containerd spawns a containerd-shim process, which invokes runc with an OCI runtime spec describing the container's config.
4
runc: namespaces + cgroups
runc calls clone()/unshare() to place the process in new pid, net, mnt, uts, and ipc namespaces, then assigns it to a cgroup with the configured memory and CPU limits.
5
pivot_root & exec
runc pivot_roots into the image's merged OverlayFS root and execs the container's entrypoint, which becomes PID 1 inside its own isolated namespace.
Interviewer Insights & Pro Tips
  • If asked 'containers vs VMs', anchor the answer on the kernel: containers share the host kernel and get isolation from namespaces/cgroups; VMs virtualize hardware and run a separate kernel entirely.
  • Mention seccomp profiles and Linux capabilities (`--cap-drop`, `--cap-add`) when discussing container security — namespaces isolate views, but capabilities and seccomp restrict what syscalls a process may make at all.
Red Flags & Common Pitfalls
  • Assuming root inside a container is equivalent to root on the host — without user namespace remapping, container root and host root share the same UID 0.
Deep-Dive Architecture & Concepts

3. Networking Modes, Volumes & Storage Drivers

Choosing a network driver and a persistence strategy are two of the most common 'design this for production' questions — both have a small number of options with very different trade-offs.

Default & User-Defined Bridge

The default bridge network has no built-in DNS and requires legacy --link flags for name resolution. A user-defined bridge network gives every container automatic DNS-based service discovery by container name.

Host Networking

The container shares the host's network namespace directly — no veth pair, no NAT, no port mapping overhead. Lowest latency, but the container binds host ports directly and loses network isolation.

Overlay Networking

Used in Swarm and conceptually mirrored by Kubernetes CNI plugins: a VXLAN-encapsulated network spans multiple hosts so containers on different machines can reach each other by virtual IP as if on one LAN.

Volumes vs Bind Mounts vs tmpfs

Named volumes are managed entirely by Docker under /var/lib/docker/volumes and are the recommended way to persist data. Bind mounts map an arbitrary host path in, useful for config/dev workflows. tmpfs mounts live in host RAM only and never touch disk.

Compose Service with a User-Defined Network, Named Volume & Resource Limits
yaml
services:
    api:
      image: myorg/api:1.4.0
      networks: [backend]
      volumes:
        - api-data:/var/lib/api        # named volume, backed by the overlay2 storage driver
        - ./config:/etc/api:ro         # bind mount, read-only, for local config injection
      deploy:
        resources:
          limits:
            memory: 512M
            cpus: "1.0"
  
  networks:
    backend:
      driver: bridge
  
  volumes:
    api-data:
      driver: local
Why it matters: The user-defined `backend` bridge network gives the `api` service DNS-based discovery for any sibling service on the same network, the named volume survives container recreation, and the resource limits are enforced as cgroup constraints on the container process.
Interviewer Insights & Pro Tips
  • When asked to compare storage drivers, lead with overlay2 (the modern default on most Linux distros) versus devicemapper (legacy, used on older RHEL/CentOS setups) and note overlay2's better performance for most workloads.
  • State explicitly that writes inside a container's own writable layer are copy-on-write and slower for write-heavy workloads — that is exactly why databases should always run against a volume, not the container's overlay filesystem.
Red Flags & Common Pitfalls
  • Publishing a port with `-p` without realizing it goes through the docker-proxy/iptables NAT path, which adds a small but measurable latency compared to host networking.
  • Relying on container IP addresses instead of DNS names for service-to-service calls — IPs change on every container recreation.
Deep-Dive Architecture & Concepts

4. Production Debugging: OOMKilled Containers, Zombie Processes & Resource Limits

The debugging questions are where interviewers separate candidates who have only read about Docker from those who have carried a pager. Two incidents come up constantly: OOMKilled containers and zombie process accumulation.

OOMKilled (Exit Code 137)

When a container's cgroup memory.current hits memory.max, the kernel's cgroup OOM killer sends SIGKILL to the largest consuming process in that cgroup. Docker reports this as exit code 137 and sets State.OOMKilled to true.

Zombie Processes & PID 1 Responsibilities

In a normal OS, init (PID 1) reaps terminated children via wait(). A container's PID 1 is usually the application itself, which rarely calls wait() on orphaned grandchildren, so they pile up as defunct zombie entries.

--init / tini as a Minimal Init

Running `docker run --init` injects a tiny init process (tini) as real PID 1, which forwards signals to the application and reaps orphaned zombies on its behalf, without requiring any application code changes.

Memory Limits Kill; CPU Limits Throttle

Exceeding a memory limit triggers the OOM killer and terminates the process. Exceeding a CPU limit (cpu.max) never kills anything — the kernel scheduler simply throttles the process's runnable time, visible as nr_throttled in cpu.stat.

Diagnosing an OOMKilled Container from the Command Line
bash
# Was this container's last exit caused by the OOM killer?
  docker inspect --format='{{.State.OOMKilled}} exit={{.State.ExitCode}}' my-service
  
  # Live memory + CPU usage compared against the limits you configured
  docker stats --no-stream my-service
  
  # Read the raw cgroup v2 accounting the kernel is actually enforcing
  cat /sys/fs/cgroup/memory.max
  cat /sys/fs/cgroup/memory.current
  cat /sys/fs/cgroup/memory.stat | grep -E "pgmajfault|oom"
  
  # Kernel-level confirmation an OOM kill happened on the host
  dmesg -T | grep -i "killed process"
Why it matters: `docker inspect` and `docker stats` give the container-level view, but cross-checking the raw cgroup files and `dmesg` confirms whether the kernel's OOM killer actually fired versus the process crashing for an unrelated reason.
Interviewer Insights & Pro Tips
  • Always ask 'was the limit set from measured usage or guessed?' — most OOMKilled incidents trace back to a memory limit copy-pasted between services with very different footprints.
  • When a container exits immediately after `docker run`, check three things in order: the exit code (`docker inspect`), whether the entrypoint process is meant to run in the foreground, and the last lines of `docker logs`.
Red Flags & Common Pitfalls
  • Restarting an OOMKilled container without raising the limit or fixing the leak — the restart policy just delays the next crash.
  • Running a multi-process container (e.g. an app plus a cron daemon) without `--init`, silently accumulating zombies until the PID table fills up.
Real-World Example

Cutting Deploy Times and Killing Repeated OOMKilled Crashes at a Fintech Startup

A Series B fintech's payments platform team ran a monolithic Node.js service built from a single-stage `node:18` image with the full build toolchain baked into production. During a promotional traffic spike, containers began restarting under OOMKilled every few minutes, and the on-call rotation was paging nightly.

  • 1Audited the existing Dockerfile with `docker history` and found no stage separation — devDependencies, the TypeScript compiler, and source maps were all shipping to production, inflating the image to 1.4GB.
  • 2Rewrote the build as a multi-stage Dockerfile: a `node:18` builder stage ran `npm ci` and `npm run build`, while a `node:18-alpine` runner stage copied in only the compiled `dist/` output and production dependencies.
  • 3Added an explicit non-root `USER` directive and switched container startup to use `--init`, since the service spawned a background worker pool whose orphaned children were accumulating as zombies.
  • 4Measured real p95 memory usage with `docker stats` over a week of production traffic and set explicit `--memory` and `--memory-swap` limits matched to that measurement instead of the previously copy-pasted defaults.
  • 5Added a CI canary step running `docker inspect --format='{{.State.OOMKilled}}'` against a load-tested container before promoting any new image to production.
Outcome: Image size dropped from 1.4GB to 210MB, build/deploy time fell by roughly 60%, and OOMKilled restarts went from about 40 per day to zero over the following month.
Real-World Interview Questions

Top Must-Know Interview Questions & Model Answers

Image Layers & Build CacheMust-Know

Q1: How do Docker image layers work, and why does instruction order in a Dockerfile matter?

Executive Answer:Each Dockerfile instruction produces an immutable, content-addressed layer, and Docker reuses cached layers only until the first instruction whose inputs changed — everything after that must rebuild.
Deep Dive Analysis:
  • The cache key for a layer is derived from the parent layer's key plus the instruction text and, for COPY/ADD, a hash of the copied files' contents.
  • Placing rarely-changing instructions (base image, OS packages, dependency manifests + install) before frequently-changing ones (application source) maximizes cache hits.
Interviewer Takeaway: Order Dockerfile instructions from least-volatile to most-volatile to keep the dependency-install layer cached across most code changes.
Dockerfile InstructionsMedium

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

Executive Answer:COPY does a plain file/directory copy from the build context; ADD does the same but additionally auto-extracts local tar archives and can fetch remote URLs.
Deep Dive Analysis:
  • ADD's implicit tar-extraction and URL-fetching behavior is considered surprising and is discouraged by Docker's own best practices for anything other than extracting a local archive.
  • COPY is the recommended default because its behavior is fully predictable and it does not silently download remote content into a layer.
Interviewer Takeaway: Default to COPY; reach for ADD only when you specifically need local tar auto-extraction.
Multi-Stage BuildsMust-Know

Q3: How does a multi-stage build reduce final image size, and what actually crosses between stages?

Executive Answer:Each FROM starts a new, independent stage; only files explicitly copied with `COPY --from=<stage>` survive into later stages, so the compiler, dev dependencies, and full source tree stay behind in the discarded builder stage.
Deep Dive Analysis:
  • A typical Node/Go/Java multi-stage build compiles or transpiles in a 'fat' stage based on a full SDK image, then copies only the compiled artifact into a slim runtime base like alpine or distroless.
  • This commonly cuts image size by 4-10x and shrinks the attack surface, since the runtime image never contains build tools, package manager caches, or source code.
Interviewer Takeaway: Multi-stage builds should be the default for any compiled or transpiled language, not an optimization applied later.
Image OptimizationMedium

Q4: What is a distroless or scratch base image, and when should you use one?

Executive Answer:Distroless images ship only an application and its runtime dependencies with no shell, package manager, or OS utilities; scratch is a completely empty base for fully static binaries.
Deep Dive Analysis:
  • Distroless dramatically shrinks the attack surface (no shell means no easy interactive shell for an attacker who gains code execution) and reduces CVE scan noise from unused OS packages.
  • The trade-off is debuggability: without a shell, you cannot `docker exec` into the container to poke around, so teams often keep a debug variant image for troubleshooting.
Interviewer Takeaway: Use distroless/scratch for security-sensitive production images, but keep a debug-enabled sibling image for local troubleshooting.
Union FilesystemHard

Q5: Explain how OverlayFS merges image layers into a single filesystem view.

Executive Answer:OverlayFS stacks each read-only image layer as a lowerdir and adds one writable upperdir for the container, presenting a merged view without physically copying every layer's files.
Deep Dive Analysis:
  • A file lookup walks the stack from the topmost writable layer down through each lowerdir until it finds the file, so higher layers transparently shadow lower ones with the same path.
  • The first modification to a file that only exists in a lower read-only layer triggers a full copy-up of that file into the upperdir before the write is applied, which is why very large single files with frequent small writes perform poorly in a container's writable layer.
Interviewer Takeaway: OverlayFS gives near-zero-cost image sharing across containers but makes copy-on-write cost a real factor for write-heavy workloads inside the writable layer.
Image OptimizationMedium

Q6: Beyond multi-stage builds, what techniques shrink a Docker image's size?

Executive Answer:Use a minimal base image (alpine/distroless), a strict .dockerignore, combine related RUN commands to avoid extra layers, and remove package manager caches within the same layer they were created.
Deep Dive Analysis:
  • Cleaning up caches in a separate RUN instruction after the install does not shrink the image, because the earlier layer already committed the cache to disk; the cleanup must happen in the same RUN statement.
  • `.dockerignore` both speeds up the build (smaller context upload) and prevents accidentally shipping `.git`, local `.env` files, or test fixtures into a layer.
Interviewer Takeaway: Layer-aware cleanup (same RUN as the install) matters as much as choosing a small base image.
Linux NamespacesMust-Know

Q7: What Linux namespaces does Docker use to isolate a container, and what does each one hide?

Executive Answer:Docker uses pid, net, mnt, uts, ipc, and (optionally) user namespaces to give the container its own process tree, network stack, filesystem root, hostname, IPC objects, and UID mapping.
Deep Dive Analysis:
  • PID namespace: the container's first process becomes PID 1 in its own tree and cannot see host or sibling-container processes.
  • Net namespace: private interfaces and routing table, connected to the host via a veth pair into a bridge.
  • Mnt namespace: a private mount table so the container's root filesystem view is independent of the host's.
Interviewer Takeaway: Namespaces answer 'what can this process see'; cgroups separately answer 'how much can it use'.
cgroups & Resource LimitsMust-Know

Q8: How do cgroups enforce resource limits on a container, and what happens at the boundary?

Executive Answer:cgroups v2 exposes controller files like memory.max, cpu.max, and pids.max per control group; the kernel enforces them directly — memory overruns trigger the OOM killer, CPU overruns throttle scheduling.
Deep Dive Analysis:
  • `docker run --memory=512m --cpus=1` translates directly into writes to that container's cgroup's memory.max and cpu.max files.
  • pids.max caps the number of processes/threads a container can fork, which is an important defense against fork-bomb style incidents inside a compromised container.
Interviewer Takeaway: Memory limits are enforced by killing; CPU limits are enforced by throttling — the failure modes look very different in monitoring.
Container RuntimeMedium

Q9: What fundamentally distinguishes a container's isolation model from a virtual machine's?

Executive Answer:Containers share the host kernel and get isolation from namespaces and cgroups; VMs virtualize hardware via a hypervisor and run an entirely separate guest kernel per instance.
Deep Dive Analysis:
  • Because containers share a kernel, container startup is near-instant (milliseconds) versus a VM's full OS boot (seconds to minutes), and container density per host is far higher.
  • The trade-off is a smaller isolation boundary: a kernel-level vulnerability can in principle be exploited across containers on the same host, whereas a VM escape requires breaking the hypervisor itself.
Interviewer Takeaway: Containers trade some isolation strength for massive gains in density and startup speed; choose VMs (or gVisor/Kata microVMs) when the isolation boundary itself is the requirement.
Container RuntimeMust-Know

Q10: Walk through exactly what happens when you run `docker run <image>`.

Executive Answer:The Docker CLI talks to dockerd over a Unix socket, dockerd delegates to containerd over gRPC, containerd spawns a containerd-shim that invokes runc, and runc creates the namespaces/cgroups and execs the entrypoint as PID 1.
Deep Dive Analysis:
  • runc builds the container per the OCI runtime spec: it calls clone()/unshare() for the namespaces, writes the cgroup limit files, and pivot_roots into the image's merged OverlayFS root.
  • The containerd-shim stays alive as the direct parent of the container process so that containerd itself can be restarted or upgraded without killing running containers.
Interviewer Takeaway: Know the chain dockerd -> containerd -> containerd-shim -> runc by name; interviewers use this to gauge whether you understand Docker's architecture beyond the CLI.
Linux NamespacesHard

Q11: What is pivot_root and why does Docker use it instead of chroot?

Executive Answer:pivot_root swaps the entire root filesystem for a process's mount namespace, completely detaching the old root, whereas chroot only changes the apparent root path while leaving the old root mounted and reachable.
Deep Dive Analysis:
  • A classic chroot escape re-mounts or references the old root via a retained file descriptor or relative path traversal; pivot_root combined with a private mount namespace removes the old root from the mount table entirely.
  • runc typically pivot_roots into the container's merged OverlayFS view and then unmounts the old root inside the new mount namespace so it is unreachable from inside the container.
Interviewer Takeaway: pivot_root plus a private mount namespace is what actually prevents a contained process from reaching the host filesystem, not just the changed root path.
Container SecurityHard

Q12: What Linux capabilities and seccomp profiles does Docker apply by default, and why do they matter?

Executive Answer:Docker drops most of the ~40 available Linux capabilities by default (keeping a minimal set like CAP_NET_BIND_SERVICE) and applies a default seccomp profile that blocks around 44 syscalls known to be dangerous or irrelevant in containers.
Deep Dive Analysis:
  • Capabilities split up what used to be all-or-nothing root privilege into fine-grained permissions (CAP_SYS_ADMIN, CAP_NET_RAW, etc.); `--cap-drop=ALL --cap-add=<specific>` follows least privilege.
  • The default seccomp profile blocks syscalls like `mount`, `reboot`, and raw `ptrace` variants that would otherwise let a compromised container affect the host or other containers even inside its namespaces.
Interviewer Takeaway: Namespaces isolate what a process sees; capabilities and seccomp restrict what syscalls it may even attempt — both layers matter for a real security answer.
Bridge NetworkingMedium

Q13: Explain the default bridge network versus a user-defined bridge network in Docker.

Executive Answer:The default bridge (docker0) has no built-in DNS and requires legacy --link flags for containers to resolve each other by name; a user-defined bridge network gives every attached container automatic DNS-based service discovery.
Deep Dive Analysis:
  • On a user-defined bridge, Docker runs an embedded DNS server that resolves container names and Compose service names to their current IPs, which is essential since container IPs change on recreation.
  • Best practice is to always create an explicit network for a multi-container application rather than relying on the default bridge.
Interviewer Takeaway: Never rely on the default bridge network for service-to-service communication in anything beyond a quick local test.
Host NetworkingMedium

Q14: What is host networking mode, and what are its trade-offs?

Executive Answer:With `--network host`, the container shares the host's network namespace directly — no veth pair, no NAT translation, no port mapping — giving the lowest possible network latency at the cost of network isolation.
Deep Dive Analysis:
  • Ports the container binds are the host's actual ports, so two containers using host mode cannot both bind the same port, and there is no per-container port remapping.
  • It is commonly used for latency-sensitive workloads (some data-plane proxies, monitoring agents) where the NAT/iptables hop of bridge networking is measurably significant.
Interviewer Takeaway: Reach for host networking only when you have measured that bridge-mode NAT overhead actually matters for your workload.
Overlay NetworkingHard

Q15: How does an overlay network let containers on different hosts communicate?

Executive Answer:An overlay network encapsulates container-to-container traffic in VXLAN tunnels between hosts, so containers on different machines appear to share a single flat virtual network regardless of the underlying physical topology.
Deep Dive Analysis:
  • Docker Swarm's overlay driver (and, conceptually, most Kubernetes CNI plugins like Calico/Flannel in overlay mode) wraps each packet with a VXLAN header carrying a virtual network identifier, then unwraps it on the receiving host.
  • This adds a small per-packet encapsulation overhead but removes the need for the physical network to know anything about individual container IPs.
Interviewer Takeaway: Overlay networking trades a small VXLAN encapsulation cost for the ability to treat a multi-host cluster's containers as one logical network.
Bridge NetworkingMedium

Q16: How does DNS-based service discovery work inside a user-defined Docker network?

Executive Answer:Docker runs an embedded DNS resolver (at 127.0.0.11 inside each container) that answers queries for other containers' names and Compose service names with their current container IP on that network.
Deep Dive Analysis:
  • Because the resolver tracks live container IPs, service discovery keeps working correctly even after a container is recreated and gets a new IP address.
  • This embedded DNS only works within a user-defined network; containers on the default bridge do not get this resolution.
Interviewer Takeaway: DNS name resolution, not static IPs, is the correct way for containers to find each other on a user-defined network.
Port PublishingMedium

Q17: What is the difference between EXPOSE in a Dockerfile and the -p flag on docker run?

Executive Answer:EXPOSE is purely documentation/metadata about which ports the container listens on and does not publish anything; -p (or --publish) actually maps a host port to a container port so external traffic can reach it.
Deep Dive Analysis:
  • EXPOSE does enable `docker run -P` (capital P) to auto-publish exposed ports to random host ports, but without either -p or -P, exposed ports are not reachable from outside the container's network.
  • Two containers on the same user-defined network can reach each other's EXPOSEd ports directly without any publishing at all.
Interviewer Takeaway: EXPOSE documents intent for inter-container access; -p is what actually opens a path from the host/outside world.
Port PublishingHard

Q18: How does docker-proxy / iptables implement published ports under the hood?

Executive Answer:Docker installs iptables DNAT rules that rewrite traffic arriving on the published host port to the container's internal IP and port; a userland docker-proxy process historically handled this before iptables rules took over most of the work.
Deep Dive Analysis:
  • The DNAT rule lives in the nat table's DOCKER chain, translating destination host:port to container-ip:port before the packet is routed to the bridge.
  • This NAT hop is exactly the small overhead that host networking mode avoids by not remapping ports at all.
Interviewer Takeaway: Published ports go through kernel-level NAT (iptables DNAT), not a simple TCP forward — knowing this explains both the small latency cost and why firewall rule ordering matters.
Volumes & Bind MountsMust-Know

Q19: What is the difference between a named volume, a bind mount, and a tmpfs mount?

Executive Answer:A named volume is fully managed by Docker under its own storage area and is the recommended way to persist data; a bind mount maps an arbitrary existing host path into the container; a tmpfs mount lives only in host memory and is never written to disk.
Deep Dive Analysis:
  • Named volumes are portable across host paths and are the only option Docker actively manages (backup, driver plugins, `docker volume` commands); bind mounts couple the container to a specific host filesystem layout.
  • tmpfs is ideal for secrets or scratch data that must never be persisted to disk, at the cost of losing the data on container stop.
Interviewer Takeaway: Default to named volumes for durable application data; use bind mounts for local dev config injection; use tmpfs for ephemeral, sensitive, in-memory-only data.
Storage DriversHard

Q20: What are storage drivers like overlay2 and devicemapper, and how do you choose between them?

Executive Answer:A storage driver implements how image layers and the container's writable layer are physically stored and merged on disk; overlay2 (built on OverlayFS) is the modern default on most Linux distributions, while devicemapper is a legacy option historically used on older RHEL/CentOS systems.
Deep Dive Analysis:
  • overlay2 generally offers better page-cache sharing and performance for typical workloads and is what Docker selects automatically when the kernel and backing filesystem support it.
  • Choosing a storage driver mainly matters when the host's kernel or filesystem doesn't support overlay2 out of the box, or for very specific I/O-pattern performance tuning.
Interviewer Takeaway: Unless you have a specific kernel/filesystem constraint, let Docker's default (overlay2) stand — it is the best-supported and best-performing option for most production hosts.
Union FilesystemMedium

Q21: Why do writes to a container's writable layer carry a copy-on-write penalty?

Executive Answer:The container's writable layer sits on top of read-only image layers; the first time a file that only exists in a lower layer is modified, the entire file must be copied up into the writable layer before the write can be applied.
Deep Dive Analysis:
  • For a large file with only small modifications, this copy-up can be a surprisingly expensive one-time cost, which is a classic reason database or log-heavy workloads should never write to the container's own filesystem.
  • Subsequent writes to the same (now copied-up) file are cheap, since the file now lives entirely in the writable layer.
Interviewer Takeaway: Route any write-heavy or persistent data path through a volume, never through the container's own writable layer.
Volumes & Bind MountsMedium

Q22: How do you persist and safely share data between multiple containers?

Executive Answer:Create a named volume and mount it into each container that needs the shared data; Docker handles the underlying storage and ensures all containers see the same consistent view.
Deep Dive Analysis:
  • For genuinely concurrent writers, the application layer still needs its own coordination (file locks, a real database) — a shared volume alone does not provide transactional consistency.
  • Volume plugins extend this pattern to network-attached storage (NFS, cloud block/file storage) for multi-host sharing beyond a single Docker host.
Interviewer Takeaway: Volumes solve where data lives, not concurrency control — plan for concurrent access at the application level separately.
OOM & Memory LimitsMust-Know

Q23: What causes a container to be OOMKilled, and how do you diagnose it after the fact?

Executive Answer:When a container's cgroup memory usage hits its configured memory.max, the kernel's cgroup OOM killer sends SIGKILL to the largest consumer in that cgroup; Docker surfaces this as exit code 137 and State.OOMKilled=true.
Deep Dive Analysis:
  • Diagnose with `docker inspect --format='{{.State.OOMKilled}}'`, cross-checked against `dmesg -T | grep -i 'killed process'` for kernel-level confirmation.
  • Common root causes: a memory limit set lower than real peak usage, a genuine memory leak, or an unbounded in-memory cache/buffer growing with traffic.
Interviewer Takeaway: Exit code 137 plus State.OOMKilled=true is the unambiguous signature of a memory-limit kill — don't confuse it with an application-level crash.
Process ManagementHard

Q24: Why do zombie processes accumulate inside containers, and how does --init fix it?

Executive Answer:A container's PID 1 is normally the application itself, which rarely calls wait() to reap terminated grandchildren the way a real init process would, so orphaned children linger as defunct zombie entries; `--init` injects a minimal init (tini) as the real PID 1 to reap them automatically.
Deep Dive Analysis:
  • Zombies consume a process table slot even though they use no CPU or memory, so enough of them accumulating can exhaust the pids.max limit or the kernel's PID space.
  • tini also correctly forwards signals like SIGTERM to the actual application process, which a naive shell-form CMD often fails to do.
Interviewer Takeaway: Any container whose main process forks children (cron, worker pools, shelling out to subprocesses) should run with `--init` unless the application itself reaps its own children.
OOM & Memory LimitsMust-Know

Q25: What happens when a container exceeds its configured memory limit versus its CPU limit?

Executive Answer:Exceeding the memory limit triggers the kernel's cgroup OOM killer, which SIGKILLs the process (exit 137); exceeding the CPU limit never kills anything — the kernel scheduler simply throttles the process's runnable time.
Deep Dive Analysis:
  • CPU throttling is visible as `nr_throttled`/`throttled_time` in cpu.stat and shows up as increased latency, not crashes — a very different failure signature from OOM kills.
  • This asymmetry means memory limits must be set close to real usage (too low = crashes), while CPU limits can be set more conservatively (too low = just slower, not fatal).
Interviewer Takeaway: Memory limits fail hard (kill); CPU limits fail soft (throttle) — size them with that asymmetry in mind.
Production DebuggingMedium

Q26: A container exits immediately after `docker run` with no obvious error. How do you debug it?

Executive Answer:Check the exit code and OOMKilled flag via `docker inspect`, read the tail of `docker logs`, and verify the entrypoint process actually runs in the foreground rather than daemonizing and returning immediately.
Deep Dive Analysis:
  • A container's lifetime is tied to PID 1; if PID 1 is a script that launches a background daemon and then exits, Docker considers the container finished even though the 'real' service thinks it's still starting.
  • Exit code 0 with no logs usually points to a foreground/daemonize mismatch; a non-zero code with a stack trace in logs points to an application-level crash.
Interviewer Takeaway: Always check exit code, OOMKilled flag, and log tail together before guessing at a root cause — the combination usually narrows it to one category of problem.
Performance DebuggingHard

Q27: How do you profile a 'noisy neighbor' container consuming excessive CPU or network on a shared host?

Executive Answer:Use `docker stats` for a live per-container view, cross-reference cgroup cpu.stat/nr_throttled for throttling evidence, and use host-level tools (pidstat, iftop, nsenter into the container's network namespace) to pin down the exact process and socket responsible.
Deep Dive Analysis:
  • `docker stats` shows aggregate CPU/memory/network per container, but pinpointing a specific runaway thread often requires `nsenter --target <pid> --pid --net` to attach host tools to the container's namespaces directly.
  • Without CPU limits (cpu.max/--cpus) set, a single misbehaving container can starve every other container scheduled on the same host, which is why resource limits are a production requirement, not an optimization.
Interviewer Takeaway: Resource limits aren't just about protecting one workload from itself — they protect every other tenant sharing the same host.
Production DebuggingMedium

Q28: Explain Docker's restart policies and how they interact with health checks.

Executive Answer:Restart policies (no, on-failure, always, unless-stopped) tell the daemon whether to relaunch a stopped container; a HEALTHCHECK instruction independently marks a running container as healthy/unhealthy, which orchestrators use for traffic routing and (in Swarm) automatic replacement.
Deep Dive Analysis:
  • Plain Docker does not restart a container purely for being 'unhealthy' — restart policies only respond to the container actually exiting; it's Swarm/Kubernetes-level orchestration that acts on health status to reroute traffic or replace instances.
  • A restart policy alone can mask a real problem: `always` will keep relaunching an OOMKilled container into the same crash loop unless the underlying limit or leak is fixed.
Interviewer Takeaway: Restart policies handle 'the container died'; health checks handle 'the container is alive but not serving correctly' — they solve different problems and both are needed in production.
Performance DebuggingHard

Q29: How do you investigate high memory usage inside a running container without restarting it?

Executive Answer:Read the live cgroup memory accounting directly (memory.current, memory.stat) alongside `docker stats`, and if the runtime supports it, take an in-process heap dump/profile (e.g. a Node.js heap snapshot or JVM heap dump) via `docker exec` without stopping the container.
Deep Dive Analysis:
  • memory.stat breaks down usage into categories (anon, file cache, kernel structures), which distinguishes a real application leak from an inflated page cache that the kernel would reclaim under pressure anyway.
  • For language runtimes with GC, correlating a rising RSS with GC logs or a heap profile pinpoints whether it's a true leak or just an under-tuned GC/allocator.
Interviewer Takeaway: Don't equate 'high RSS' with 'leak' automatically — break down memory.stat first to see how much is reclaimable cache versus genuinely unreleased application memory.
Common Mistakes

Mistakes That Sink Otherwise Strong Candidates

Shipping a single-stage image with the full build toolchain and dev dependencies to production.

Why it happens: It's the fastest way to get a working Dockerfile early in a project, and nobody revisits it once the app is 'working'.

The fix: Convert to a multi-stage build: compile in a fat builder stage, copy only the compiled output and production dependencies into a slim runtime stage.

Running the application process as root inside the container.

Why it happens: Docker's default USER is root unless a Dockerfile explicitly sets otherwise, and it 'just works' in development.

The fix: Add an explicit non-root `USER` in the Dockerfile and, where the platform supports it, enable user namespace remapping so container root doesn't map to host root.

Tagging and deploying images as `latest` in production.

Why it happens: It's the default tag and avoids the friction of managing version numbers during rapid iteration.

The fix: Pin production deployments to immutable semantic version tags or image digests so a rollback is always possible and 'latest' can't silently change underneath a running service.

Skipping .dockerignore, letting `.git`, `node_modules`, and local `.env` files into the build context.

Why it happens: It's an easy step to forget when a project is scaffolded quickly, and the build still 'works' without it.

The fix: Add a `.dockerignore` mirroring `.gitignore` plus build artifacts, and audit layers with `docker history` to confirm nothing sensitive slipped in.

Leaving containers without memory or CPU limits in production.

Why it happens: Local development never hits real memory pressure, so limits feel like unnecessary friction until an incident happens.

The fix: Measure real p95 usage with `docker stats` under production-like load, then set `--memory`/`--cpus` (or Kubernetes requests/limits) from that data, not from guesses.

Running multiple unrelated processes in one container without a real init system.

Why it happens: Teams migrating from VM-based deployments carry over the 'one box runs everything' mental model.

The fix: Split into one process per container wherever possible; where a supporting process (cron, log shipper) is unavoidable, run with `--init` so zombies get reaped and signals get forwarded correctly.

Writing persistent application state into the container's own writable layer instead of a volume.

Why it happens: It's the path of least resistance during a quick prototype, and the data 'appears' to persist as long as the container isn't removed.

The fix: Mount a named volume for any data that must survive container recreation, and treat the container's writable layer as fully disposable.

Ignoring SIGTERM in the application, forcing Docker to wait out the full stop timeout and SIGKILL it.

Why it happens: Many language runtimes and shell-form CMD instructions don't forward or handle termination signals by default.

The fix: Use exec-form CMD/ENTRYPOINT (`["node", "server.js"]` not `node server.js`) and add a SIGTERM handler that closes connections and exits cleanly.

Copying the entire source tree before installing dependencies in a Dockerfile.

Why it happens: It reads simpler top-to-bottom and works fine for the very first build.

The fix: Copy only dependency manifest files first, run the install, then copy the rest of the source — so unrelated code changes don't invalidate the dependency-install cache layer.

Assuming a container's IP address is stable and hardcoding it for service-to-service calls.

Why it happens: It happens to work during a local test session where nothing gets recreated.

The fix: Use DNS-based service discovery on a user-defined network (container name or Compose service name) instead of raw IPs.

Cheat Sheet

Quick-Reference Cheat Sheet

Core CLI Commands
docker build -t <tag> .Build an image from the Dockerfile in the current context.
docker run -d --init --name <n> <image>Run detached with an init process to reap zombies.
docker ps -aList all containers, including stopped ones.
docker logs -f <container>Stream a container's stdout/stderr logs.
docker exec -it <container> shOpen an interactive shell inside a running container.
docker inspect <container>Dump full JSON config, state, exit code, and OOMKilled flag.
docker stats --no-streamSnapshot CPU, memory, and network usage per container.
docker system prune -a --volumesReclaim disk space by removing unused images, containers, and volumes.
Dockerfile Instructions
FROM <image> AS <stage>Start a new build stage from a named base image.
COPY --from=<stage>Copy files from an earlier build stage into the current one.
RUN vs CMD vs ENTRYPOINTRUN executes at build time; CMD/ENTRYPOINT define the runtime process.
USER <name>Switch to a non-root user for subsequent instructions and runtime.
HEALTHCHECKDefine a command Docker runs periodically to mark the container healthy/unhealthy.
ARG vs ENVARG is build-time only; ENV persists into the running container.
Networking Modes Compared
bridge (default)Isolated network via docker0; no built-in DNS, requires --link for name resolution.
user-defined bridgeIsolated network with embedded DNS-based service discovery by container name.
hostShares the host's network namespace directly; no NAT, lowest latency, no port isolation.
overlayVXLAN-encapsulated network spanning multiple hosts, used by Swarm/K8s CNI.
noneContainer gets a loopback interface only, fully network-isolated.
Storage & Volumes
named volumeDocker-managed persistent storage under /var/lib/docker/volumes; recommended default.
bind mountMaps an arbitrary host path into the container; couples container to host layout.
tmpfs mountIn-memory only, never touches disk; good for secrets/scratch data.
overlay2 storage driverModern default union filesystem driver for image and container layers.
Resource Limits & Debugging Signals
--memory / --memory-swapSet the cgroup memory.max ceiling; exceeding it triggers the OOM killer.
--cpus / --cpu-sharesSet cgroup cpu.max/weight; exceeding it throttles, does not kill.
--pids-limitCaps process/thread count per container; defends against fork bombs.
exit code 137SIGKILL received — almost always an OOM kill; confirm via State.OOMKilled.
exit code 143SIGTERM received and honored — a clean, expected shutdown.
Assessment Integration

Recommended Practice Quizzes on QuizCluster

Test your retention and prepare for timed live coding and MCQ technical screening rounds:

Frequently Asked Questions

Do Docker interviews expect Kubernetes knowledge too?

For most Cloud & DevOps roles, yes — interviewers expect you to know where Docker's responsibility ends (building and running a single container) and where an orchestrator like Kubernetes takes over (scheduling, scaling, service discovery across a cluster). Deep Docker internals knowledge is still evaluated on its own, especially for debugging questions.

Is it still relevant to learn `docker-compose` given Kubernetes' popularity?

Yes. Compose remains the standard for local development and small single-host deployments, and interviewers frequently use a docker-compose.yml as a quick way to probe your understanding of networking, volumes, and resource limits without requiring a full cluster.

How deep into the Linux kernel do I need to go for a Docker interview?

You should be able to name and explain namespaces (pid, net, mnt, uts, ipc) and cgroups confidently, and describe the docker run to runc call chain. You generally do not need kernel source-level detail unless interviewing for a container runtime or platform infrastructure team specifically.

What's the single most common production Docker debugging scenario to prepare for?

OOMKilled containers (exit code 137). Interviewers use it because it tests whether you understand cgroup memory accounting, can read `docker inspect` and `dmesg` output, and know the difference between a memory leak and an undersized limit.

Explore Other Preparation Guides

Software Engineering
How to Prepare for SDE Interview: Complete 2026 Roadmap
16 min readRead →
Java Ecosystem
How to Prepare for Java Developer Interview: Core to Spring Boot & JVM
18 min readRead →
Microservices & Distributed Systems
How to Prepare for Microservices Developer Interview: Distributed Architecture & Cloud
17 min readRead →
System Design
System Design Interview Guide: Complete 2026 Roadmap
21 min readRead →
Databases
SQL Interview Questions & Preparation Guide: Beginner to Advanced
17 min readRead →
Programming Languages
Python Interview Preparation: Complete Guide for 2026
17 min readRead →
Frontend Engineering
React Interview Preparation: React 19 & Next.js Guide
17 min readRead →
Cloud & DevOps
Kubernetes Interview Guide: Architecture, Pods, Networking & Troubleshooting
17 min readRead →
Cloud & DevOps
AWS Solutions Architect Interview Guide: Real Architecture Scenarios
17 min readRead →
Databases
Database System Design: SQL vs NoSQL, Sharding, Replication & Indexing
19 min readRead →
Microservices & Distributed Systems
Kafka Interview Guide: Architecture, Consumers, Partitions & Exactly-Once Semantics
17 min readRead →
Backend Engineering
REST API Design Interview Guide: Authentication, Pagination, Versioning & Rate Limiting
15 min readRead →
Programming Languages
JavaScript & TypeScript Interview Guide: From Closures to the Event Loop
17 min readRead →
Backend Engineering
Node.js Backend Interview Guide: Event Loop, Streams, APIs & Scaling
17 min readRead →
Databases
Redis System Design Guide: Caching, Eviction, Persistence & Distributed Locks
17 min readRead →
Software Engineering
Concurrency Interview Guide: Threads, Locks, Race Conditions & Deadlocks
17 min readRead →
Software Engineering
Dynamic Programming Patterns: How to Recognize and Solve DP Problems
17 min readRead →