Why Containers
The overview named the disease: environment drift, the gap between where software is built and where it runs. This page is about the cure, and about why this particular cure — and not the obvious older one — won.
The older cure was the virtual machine. To understand why containers exist, you have to understand what a VM does well, what it does badly, and which of those problems containers actually fix.
What we actually want
Section titled “What we actually want”Strip the problem to first principles. We want three things from “shipping software”:
- Reproducibility — the environment the app runs in is identical everywhere, every time.
- Isolation — one app can’t see, corrupt, or starve another app on the same machine.
- Density & speed — we can pack many apps onto one machine and start them in milliseconds, because compute costs money and deploys happen constantly.
A virtual machine delivers (1) and (2) beautifully. It fails (3). Containers were invented to keep (1) and (2) while winning (3) — and the reason they can is a single architectural difference.
The architectural difference: shared kernel
Section titled “The architectural difference: shared kernel”A virtual machine virtualizes hardware. The hypervisor presents fake CPUs, fake disks, and fake network cards to a complete guest operating system — its own kernel, its own boot process, its own gigabytes of OS. You’re running a whole computer inside your computer.
A container shares the host’s kernel. There is no guest OS, no boot, no virtual hardware. A container is just processes running on the host, isolated by kernel features (which the next page takes apart). The “operating system” inside a container image is only the userland files — libraries and tools — not a second kernel.
VIRTUAL MACHINES CONTAINERS ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ app │ │ app │ │ app │ │ app │ │ app │ │ app │ │ libs │ │ libs │ │ libs │ │ libs │ │ libs │ │ libs │ │ GUEST│ │ GUEST│ │ GUEST│ ← heavy └──────┘ └──────┘ └──────┘ │ OS │ │ OS │ │ OS │ (just processes + isolation) └──────┘ └──────┘ └──────┘ ┌───────────────────────────┐ ┌───────────────────────┐ │ shared host kernel │ │ hypervisor │ └───────────────────────────┘ ├───────────────────────┤ ┌───────────────────────────┐ │ host kernel │ │ host hardware │ └───────────────────────┘ └───────────────────────────┘That one decision — don’t duplicate the kernel — is the whole story. Removing the guest OS removes gigabytes of image, removes a full boot sequence, and removes the memory cost of a second kernel per app. The result:
| Virtual machine | Container | |
|---|---|---|
| Isolation boundary | Virtualized hardware (hypervisor) | Kernel namespaces & cgroups |
| Guest OS / kernel | Full, per VM | None — shares host kernel |
| Typical size | Gigabytes | Megabytes |
| Start time | Seconds to minutes | Milliseconds |
| Density per host | Tens | Hundreds to thousands |
| Isolation strength | Stronger (hardware-level) | Weaker (kernel-level) |
| Runs a different OS kernel? | Yes (Windows on Linux, etc.) | No (Linux kernel only) |
The payoff: the artifact that runs identically everywhere
Section titled “The payoff: the artifact that runs identically everywhere”Here is the concept that makes containers matter for DevOps. When you build a container, you produce a single, immutable, content-addressed image: the app plus its libraries plus its config, frozen together. That image is the artifact. You build it once, and then:
build once run the SAME image everywhere ┌───────────┐ push ┌──────────┐ ┌──────────┐ ┌──────────┐ │ image │ ─────────► │ laptop │ │ staging │ │ prod │ │ sha256:ab…│ └──────────┘ └──────────┘ └──────────┘ └───────────┘ identical bytes, every environmentThis is the recurring thread of the whole book made concrete. The old way to “promote to production” was a sequence of manual, error-prone steps — re-run the install script on the new box, hope the package versions resolve the same, copy the config by hand, pray. Every one of those steps is a place where staging and production silently diverge. The container image deletes all of them: you don’t rebuild the environment in production, you run the exact bytes you already tested. There is nothing to get wrong in transit, because nothing is reassembled in transit. That is how containers make production safer — they collapse “build, then rebuild, then rebuild again” into “build once, run anywhere,” and a bug can no longer crawl into the gaps between rebuilds.
It also gives you a clean rollback: a previous image is just another immutable artifact sitting in a registry. Rolling back is running an older known-good image, not unwinding a pile of in-place changes.
When a VM is still the right tool
Section titled “When a VM is still the right tool”Containers don’t make VMs obsolete. Use a VM (or a VM around containers) when you need a different OS kernel than the host, when you need the stronger hardware-level isolation boundary for untrusted or strictly-compliant workloads, or when you’re running a monolithic legacy system that assumes it owns a whole machine. In practice the modern stack uses both: VMs carve a cloud into machine-sized units, and containers pack many apps densely onto each one.
The architect’s lens
Section titled “The architect’s lens”Step back from how containers work and answer the five questions that separate an architect from an operator:
- Why does it exist? Because we want three things from shipping software — reproducibility, isolation, and density/speed — and the older cure (the VM) nailed the first two but failed the third. Containers exist to keep reproducibility and isolation while winning density, by sharing the host’s kernel instead of duplicating a whole guest OS.
- What problem does it solve? Environment drift — the gap between where software is built and where it runs. The immutable, content-addressed image lets you run the exact bytes you tested in every environment, deleting the manual “reassemble it in prod” steps where staging and production silently diverge.
- What are the trade-offs? You trade isolation strength for density and millisecond starts. The boundary is the shared kernel, so a kernel/runtime bug can cross it (CVE-2019-5736 was a real container-to-host escape). You’re also Linux-kernel-only, and you take on image, registry, and orchestration machinery you didn’t have before.
- When should I avoid it? When you need a different OS kernel than the host; when untrusted, multi-tenant, or strict-compliance workloads need hardware-level isolation (wrap them in a VM); or for a monolithic legacy system that assumes it owns a whole machine.
- What breaks if I remove it? You fall back to per-machine setup: environment drift returns, promote-to-prod becomes hand-run install scripts and copied config, density collapses from hundreds of apps per host to tens, and rollback turns from “run the previous image” back into unwinding a pile of in-place changes.
Check your understanding
Section titled “Check your understanding”- List the three things we want from “shipping software.” Which one does a VM fail at, and why?
- What is the single architectural difference between a VM and a container, and what three costs does it remove?
- A container is “weaker” isolation than a VM. Weaker how, and why does anyone accept that trade?
- Explain “build once, run anywhere” in terms of the artifact. What manual steps does it delete from promoting code to production, and how does that make production safer?
- Give two situations where you’d still reach for a virtual machine instead of (or around) a container.
Show answers
- Reproducibility, isolation, and density & speed. A VM nails reproducibility and isolation but fails density & speed: virtualizing whole hardware means a full guest OS, gigabytes of image, and a multi-second boot per app.
- The container shares the host’s kernel instead of virtualizing hardware and running a full guest OS. Removing the guest OS removes three costs: gigabytes of image size, a full boot sequence, and the memory of a second kernel per app.
- Weaker because a container’s boundary is the shared kernel, so a kernel bug could in principle let a process escape onto the host — whereas a VM’s hypervisor boundary is hardware-level. People accept the trade for the enormous gains in density and start-up speed (and wrap untrusted workloads in lightweight VMs when they need the stronger boundary).
- You build a single immutable, content-addressed image once and run those exact bytes in every environment. It deletes the manual promote-to-prod steps — re-running install scripts, hoping package versions resolve identically, hand-copying config — so nothing is reassembled in transit and a bug can’t crawl into the gaps between rebuilds.
- When you need a different OS kernel than the host (e.g. Windows on Linux); when you need the stronger hardware-level isolation for untrusted or strictly-compliant workloads; or for a monolithic legacy system that assumes it owns a whole machine. (Any two.)