Containers vs. VMs: What the Kernel Actually Isolates
A junior engineer says "a container is just a lightweight VM." Explain precisely why this analogy is wrong, and describe what a container actually *is* at the process level on the host.
Browse questions, read quick answers, or expand full article breakdowns on demand. Filter by level, domain, or completion status using the sidebar dashboard.
A junior engineer says "a container is just a lightweight VM." Explain precisely why this analogy is wrong, and describe what a container actually *is* at the process level on the host.
You run `docker build` twice on the same Dockerfile with no changes and notice the second build finishes in under a second. Explain what mechanism makes this possible and what is actually being reused.
You run `docker build` twice on the same Dockerfile with no changes and notice the second build finishes in under a second. Explain what mechanism makes this possible and what is actually being reused.
A junior engineer says "a container is just a lightweight VM." Explain precisely why this analogy is wrong, and describe what a container actually *is* at the process level on the host.
A junior engineer says "a container is just a lightweight VM." Explain precisely why this analogy is wrong, and describe what a container actually *is* at the process level on the host.
A junior engineer says "a container is just a lightweight VM." Explain precisely why this analogy is wrong, and describe what a container actually *is* at the process level on the host.
You run `docker build` twice on the same Dockerfile with no changes and notice the second build finishes in under a second. Explain what mechanism makes this possible and what is actually being reused.
Your team's Dockerfile uses both `ENTRYPOINT` and `CMD`. A teammate wants to remove `CMD` because "it seems redundant." Explain why removing it would change the container's runtime behavior and give a scenario where this distinction matters operationally.
A developer mounts their local project folder into a container with `-v` during development, but in the production Dockerfile they use `COPY`. Explain why these two approaches exist and why you would never rely on a bind mount in a production deployment.
A container running a database is removed with `docker rm`, and the team is shocked their data disappeared. Diagnose what likely went wrong in their setup and explain the container filesystem lifecycle that caused this.
A service inside a container listens on port 8080, and it's exposed with `-p 80:8080`. Walk through what actually happens to a packet arriving at the host's port 80 to reach the process inside the container.
Two engineers build the same application — one from `ubuntu:latest`, another from `alpine`. Beyond image size, what practical engineering trade-offs should inform this base image decision?
A teammate adds application logging to a file inside the container instead of stdout/stderr, then complains `docker logs` shows nothing. Explain the underlying logging model Docker expects and why their approach breaks it.
Explain, namespace by namespace (PID, NET, MNT, UTS, IPC, USER), what specific kernel isolation each one provides to a container, and describe a real scenario where sharing one particular namespace between containers (e.g., `--pid=container:x`) is a deliberate and useful debugging technique.
A container set with `--memory=512m` gets OOM-killed even though `docker stats` shows it using only 300MB of RSS at the time. Explain what other memory accounting cgroups tracks (page cache, kernel memory) that could explain this, and how you'd investigate.
A build suddenly takes much longer after a teammate added a large `node_modules` folder to the project directory, even though it's never referenced in the Dockerfile. Explain what's happening and how to fix it.
A build suddenly takes much longer after a teammate added a large `node_modules` folder to the project directory, even though it's never referenced in the Dockerfile. Explain what's happening and how to fix it.
Diagram (in words) the full process chain from a `docker run` invocation down to the actual container process, explicitly naming the role of `dockerd`, `containerd`, `containerd-shim`, and `runc`, and explain why the shim's existence allows the Docker daemon to be restarted without killing running containers.
A container needs to bind to a privileged port and adjust system time as part of its function, but your security policy forbids `--privileged`. Explain the capability-based alternative, name the specific capabilities required, and articulate why granular capability grants are architecturally superior to the privileged flag.
Your application needs different database URLs in staging versus production, but you want to use the exact same image in both. Explain the mechanism that makes this possible without rebuilding the image, and why baking config into the image is considered an anti-pattern.
Your application needs different database URLs in staging versus production, but you want to use the exact same image in both. Explain the mechanism that makes this possible without rebuilding the image, and why baking config into the image is considered an anti-pattern.
You create a custom bridge network and notice new `iptables` chains and rules appear on the host without you touching `iptables` directly. Explain what Docker is doing to the NAT and FILTER tables to make inter-container and container-to-external routing work, and describe a scenario where a conflicting host firewall rule could silently break container connectivity.
In a multi-host overlay network (e.g., Swarm), two containers on different physical hosts communicate directly by container IP. Explain the encapsulation mechanism (VXLAN) that makes this possible at the packet level, and identify the MTU-related failure mode this commonly introduces.
A container running a simple shell script exits immediately after starting, even though the script "runs forever" when tested locally. What container lifecycle rule explains this behavior?
A container running a simple shell script exits immediately after starting, even though the script "runs forever" when tested locally. What container lifecycle rule explains this behavior?
A service configured with `--cpus=2` shows periodic latency spikes even though average CPU usage sits well under 200%. Explain how CFS (Completely Fair Scheduler) quota-based throttling within a fixed period can cause this, and why average utilization metrics can be misleading here.
A container running as an unprivileged process is still able to see and signal processes on the host when run with `--pid=host`. Explain precisely what isolation guarantee is being intentionally broken, and describe one legitimate production use case where this trade-off is justified.
Your team deploys using the `latest` tag and occasionally ends up with different code running on different hosts despite "not changing anything." Explain why tag-based deployment without immutable references is risky in production.
Your team deploys using the `latest` tag and occasionally ends up with different code running on different hosts despite "not changing anything." Explain why tag-based deployment without immutable references is risky in production.
A hardened production seccomp profile blocks an application from calling `clone()` with certain flags, causing an obscure runtime crash unrelated to any obvious security feature. Explain how seccomp filters operate at the syscall level, why default Docker seccomp profiles allow most syscalls but block dangerous ones, and how you'd methodically identify which specific syscall is being blocked.
Contrast what AppArmor's mandatory access control profile enforces versus what seccomp enforces for the same container, and describe a concrete attack scenario that seccomp alone would not stop but a correctly scoped AppArmor profile would.
Your Go application's final image is 900MB because it includes the full compiler toolchain. Design a multi-stage build that resolves this, and explain precisely what gets carried between stages and what gets discarded.
Your Go application's final image is 900MB because it includes the full compiler toolchain. Design a multi-stage build that resolves this, and explain precisely what gets carried between stages and what gets discarded.
Your Go application's final image is 900MB because it includes the full compiler toolchain. Design a multi-stage build that resolves this, and explain precisely what gets carried between stages and what gets discarded.
You need to perform a zero-downtime Docker Engine upgrade on a host running stateful production containers. Explain how `live-restore` allows containers to keep running through a daemon restart, what it does *not* protect against, and what happens if the containerd/shim architecture itself needs an upgrade at the same time.
A production incident requires live debugging of a distroless container that has no shell, no package manager, and no coreutils. Walk through at least two distinct real-world techniques (e.g., ephemeral debug containers sharing namespaces, sidecar attach) to inspect its running process, filesystem, and network state without modifying the original image.
A Dockerfile does `COPY . .` before `RUN npm install`. Every single code change forces a full dependency reinstall during CI, adding 4 minutes per build. Explain the caching mechanic causing this and restructure the instructions to fix it.
A Dockerfile does COPY . . before RUN npm install. Every single code change forces a full dependency reinstall during CI, adding 4 minutes per build. Explain the caching mechanic causing this and restructure the instructions to fix it.
A maximally hardened internet-facing container profile combines capability dropping, a custom seccomp filter, an AppArmor/SELinux MAC profile, a read-only root filesystem, and no-new-privileges — five distinct controls, each closing off a different class of breakout or escalation technique rather than one control doing everything.
No single control covers the full space of container-escape and privilege-escalation techniques, because those techniques operate at different layers: identity/capability, syscall availability, resource-path access, filesystem mutability, and privilege-acquisition mechanics. A genuinely hardened profile stacks controls that each neutralize a specific class, so that defeating the whole profile requires simultaneously bypassing several independent mechanisms.
1. --cap-drop=ALL + minimal --cap-add
Strips every root capability by default, re-adding only what's demonstrably required (e.g. NET_BIND_SERVICE for a privileged port). This specifically neutralizes capability-based escalation — techniques that rely on the container process still holding capabilities like CAP_SYS_ADMIN (which can enable remounting filesystems, manipulating namespaces, or abusing certain mount/unshare calls to break isolation), CAP_SYS_MODULE (loading arbitrary kernel modules — a direct path to full host compromise), or CAP_SYS_PTRACE (attaching to and manipulating other processes' memory). Without these, whole categories of published container-escape exploits simply have no capability to work with, regardless of what other bugs exist.
2. A custom seccomp profile
Layered on top of (or replacing) Docker's default, restricting the syscall surface further than the general-purpose default allows for this specific workload. This neutralizes dangerous syscall invocation directly — mount()/umount2() (filesystem manipulation attempts), kernel module syscalls, unshare()/clone() with namespace-creation flags (nested namespace tricks used in several real container-escape CVEs), and ptrace()-family calls if not otherwise needed. Even if an attacker gets arbitrary code execution inside the container, a properly scoped seccomp filter means many exploit primitives simply return EPERM before they can do anything.
3. AppArmor (or SELinux) MAC profile
Enforces path- and resource-scoped access control beneath the syscall-availability layer. This neutralizes resource-targeted exploitation that stays within an otherwise-permitted syscall — reading secrets or config outside the app's legitimate working set, executing unexpected binaries that happen to be present in the image, or writing to paths that shouldn't be reachable even by a permitted syscall like open()/write(). This is precisely the gap seccomp structurally cannot close (see the seccomp-vs-AppArmor scenario), so its absence would leave every path-scoped attack completely unaddressed regardless of how tight the other controls are.
4. Read-only root filesystem (--read-only, with explicit writable volumes/tmpfs only where genuinely needed)
Makes the container's own image filesystem immutable at runtime. This neutralizes persistence and tooling-drop techniques — an attacker who gets code execution can't write a malicious script, a reverse-shell binary, or a cron-triggerable payload into the container's filesystem to survive or expand the compromise, because there's nowhere writable to put it outside the explicitly declared exceptions (which should be as narrow as /tmp or a specific app data directory, mounted as tmpfs or a scoped volume rather than leaving the whole filesystem open).
5. --security-opt=no-new-privileges
Sets the kernel's PR_SET_NO_NEW_PRIVS flag, which prevents any process in the container — including ones spawned via execve() — from gaining privileges it didn't already have, most importantly blocking setuid/setgid binary privilege re-acquisition. Without this, a process that's had its capabilities carefully dropped could still regain elevated privileges by executing a setuid-root binary present in the image (intentionally or via a supply-chain-planted one), completely undoing the capability-dropping work from control #1. This is the control that specifically closes the "gained privileges back via exec" class of escalation that the other four don't directly address.
Each control addresses a technique class the others don't:
| Control | Class of Technique Neutralized |
|---|---|
| Capability dropping | Direct abuse of retained root capabilities (mount tricks, module loading, ptrace abuse) |
| seccomp | Invocation of dangerous syscalls regardless of capability state |
| AppArmor/SELinux | Path/resource-scoped access to secrets or unexpected binaries within allowed syscalls |
| Read-only root filesystem | Persistence and malicious tooling drop into the image filesystem |
| no-new-privileges | Privilege re-acquisition via setuid binaries or exec-time privilege gain |
An attacker defeating this profile has to find a technique that simultaneously doesn't require a dropped capability, doesn't touch a blocked syscall, doesn't touch a path AppArmor restricts, doesn't need to write anything to disk, and doesn't rely on regaining privilege through exec — a genuinely narrow remaining surface compared to a default, unhardened container.
clone()-blocking scenario) before it stops a single real attack.USER directives in the image itself — the controls here restrict what a process can do, but running as an unprivileged UID to begin with removes an entire tier of assumptions attackers rely on.docker inspect and a startup smoke test should confirm capabilities, seccomp profile, AppArmor profile, read-only status, and no-new-privileges are all actually applied as configured, since a single silently-dropped flag re-opens exactly the class of attack that control was meant to close.Each hardening control closes a distinct, non-overlapping class of attack technique, so a maximally hardened profile is a deliberate stack of five independent barriers, not one strong setting doing all the work.
A high-throughput edge service running in containers starts silently dropping new connections under load, and `dmesg` shows conntrack table full errors. Explain how Docker's iptables-based NAT interacts with the kernel's connection tracking table, why containerized workloads are especially prone to exhausting it, and the tuning levers available.
Standard CPU/memory utilization metrics look healthy, but application-level latency at the edge is degrading under load. Explain how Pressure Stall Information (PSI) in cgroups v2 exposes resource contention that traditional utilization metrics miss, and how you'd use it to diagnose whether the bottleneck is CPU, memory, or I/O contention.
Even after reordering Dockerfile instructions for cache-friendliness, your team's CI runners (ephemeral, ban `--cache-from` layer reuse) still reinstall dependencies every run. What BuildKit feature addresses this specific problem, and how does it differ from ordinary layer caching?
Even after reordering Dockerfile instructions for cache-friendliness, your team's CI runners (ephemeral, ban `--cache-from` layer reuse) still reinstall dependencies every run. What BuildKit feature addresses this specific problem, and how does it differ from ordinary layer caching?
Explain, at a mechanistic level, how a historical runc container-breakout vulnerability (such as the `/proc/self/exe` file-descriptor overwrite class of CVEs) allowed a malicious container to overwrite the host `runc` binary, and what defense-in-depth layers (user namespaces, read-only host binaries, monitoring) would have limited the blast radius even if the specific CVE were unpatched.
During a rolling deployment, containers are hard-killed after a 10-second timeout, dropping in-flight requests, even though the application has SIGTERM handling implemented correctly. Diagnose the most likely architectural cause involving PID 1, process supervision, and signal propagation inside the container, and describe the fix.
Two containers on the default `bridge` network can't resolve each other by container name, but two containers on a user-defined bridge network can. Explain the underlying difference in how Docker handles DNS resolution between these two network types.
Two containers on the default `bridge` network can't resolve each other by container name, but two containers on a user-defined bridge network can. Explain the underlying difference in how Docker handles DNS resolution between these two network types.
A latency-sensitive service performs noticeably better under `--network host` than the default bridge network. Explain the actual network path difference that causes this performance gap, and identify the operational trade-off the team is accepting by using host networking.
A `docker-compose.yml` uses `depends_on` to ensure the database container starts before the API container, but the API still crashes on startup trying to connect. Explain why `depends_on` alone doesn't solve this problem and what actually needs to happen.
A container runs as root by default, and a file it writes to a bind-mounted volume ends up owned by `root` on the host, breaking the host user's ability to edit it. Explain the UID mapping reality behind this, and describe two distinct strategies to prevent it.
Your security team mandates that no production container may run as UID 0. Walk through what changes are required in the Dockerfile and what operational issues (port binding, file permissions, package installs) commonly break as a result — and how to resolve each.
A container intermittently fails to resolve an external hostname under load, though `curl` works fine most of the time. Explain how Docker's embedded DNS resolver works and a plausible root cause for intermittent resolution failures.
A Compose stack reports all containers as "running," yet the application is non-functional because the API started before the database finished initializing. Design a `HEALTHCHECK`-based solution and explain how it changes container state reporting versus a plain process check.
Your CI logs show the build context being sent to the daemon is 1.2GB despite a small application. Explain the mechanism by which this bloat occurs and how `.dockerignore` interacts with the build process to prevent it.
A team migrating from a single Docker host to a small Swarm/multi-host setup discovers that named volumes don't "follow" a rescheduled container to another node. Explain why this happens and what class of solution is required.