Chapter 1: Foundations
Topic 01

Why Containers Exist

ConceptContainers

A container packages an application together with everything it needs to run — its code, its runtime, the system libraries it links against, and the file layout it expects — into one image that runs the same on a laptop, a CI runner, and a production server. You build that image once and run it anywhere a container engine exists, and what you tested is byte-for-byte what you ship.

It exists to kill a specific, expensive class of failure: the app that runs in development and dies in production because the two machines differ in a library version, an environment variable, or a system package nobody wrote down. The container carries its world with it, so "it worked on my machine" stops being an excuse and starts being a guarantee.

The "Works on My Machine" Problem

An application depends on far more than the code in its repository. It needs a particular interpreter or runtime version, a set of shared libraries at specific versions, a locale, a handful of environment variables, and a directory layout it can find its files in. None of that lives in the source tree, and most of it is invisible until it is wrong. Ship only the code to a machine configured even slightly differently, and the gaps surface as bugs that reproduce in production and nowhere else.

The sharpest version is a dependency that is present on the developer's laptop and absent — or a different version — on the server. The app imports a library that links against a system package compiled into the developer's OS; the production host has an older one, and a function silently behaves differently. There is no error at deploy time, just wrong output at 3am. The container removes the variable entirely by carrying that system package inside the image, so the runtime environment travels with the code instead of being assumed.

The Deployment Unit

A container image is a single, versioned artifact that holds the application and its dependencies. That changes what "promote to production" means. Instead of re-installing the app on a new box and hoping the install reproduces the same environment, you take the exact image that passed tests in staging and run it in production unchanged. The thing you verified and the thing you run are the same bytes, identified by the same digest.

This is the property that makes containers worth the trouble. A build pipeline produces one image; staging runs it; production runs the identical image; a rollback is just running the previous image again. "Works in staging" and "works in production" collapse into one statement, because there is only one artifact moving through the stages — not a recipe re-cooked on each machine.

Why the same code behaves differently — and how an image fixes it
Ship only the code
The runtime, libraries, and system packages are assumed to match on every machine. They rarely do — and the gap is a production-only bug.
Ship a container image
Code plus its runtime, libraries, and file layout travel as one artifact. The same bytes run in dev, CI, and prod.

Isolation Without a Full Operating System

A container gets its own view of the filesystem, its own process tree, and its own network interfaces — but it shares the host's kernel rather than booting one of its own. That is the difference from a virtual machine, and it is the reason a container starts in milliseconds and adds almost no overhead. There is no guest operating system to boot, no emulated hardware, just a process the kernel has been told to fence off. The next topic takes that comparison apart in detail.

Because the isolation is cheap, the unit of deployment can be small. You package one service per container instead of standing up a whole machine for it, and a single host comfortably runs dozens of containers where it would run a handful of VMs. The packaging win and the density win come from the same fact: a container is a process, not a computer.

Density and Disposability

Containers are cheap to start and cheap to throw away, and that reshapes how you operate them. The model is cattle, not pets: when a container misbehaves, you do not SSH in and repair it — you kill it and start a fresh one from the same image. Whatever drift accumulated in the broken one is discarded with it, and the replacement is in a known-good state because it came from the image, not from a sequence of live edits.

This is the mental shift that trips up engineers coming from long-lived servers. A container is not a small machine you maintain; it is a disposable instance of an image. Anything you change inside a running container is lost the moment it is replaced — which is exactly why durable data has to live outside the container, a point this course returns to when it covers volumes.

Where Driftwood Comes In

The running example for this book is Driftwood, a small bookmark-sharing web application. Today it runs on its author's laptop against a specific Python version with a specific set of installed libraries, and deploying it means a page of setup instructions and a prayer that the server matches. That is the snowflake this course dismantles.

Over the next chapters Driftwood becomes a container image — first naively, then built well — that runs identically on any host with a container engine. By the end it is a hardened, slim image shipped through a registry by a CI pipeline, with its database and reverse proxy as their own containers. The first step is the one this chapter sets up: understanding what a container actually is before packaging anything into one.

Common Mistakes
  • Treating a container like a tiny VM you SSH into and hand-edit — the moment you fix something live instead of rebuilding the image, you recreate the snowflake containers were meant to abolish, and the fix vanishes on the next replacement.
  • Assuming a container ships its own kernel — it does not; it uses the host's kernel, which is why a Linux container needs a Linux kernel and why Docker Desktop quietly runs a Linux VM on macOS and Windows.
  • Putting application data inside the container's filesystem — when the container is replaced, the data goes with it; durable state must live in a volume outside the container, not in the disposable instance.
  • Reaching for containers to paper over undeclared dependencies — if the app relies on whatever happens to be installed on the host, a container just freezes the mess; the win comes from declaring everything the app needs inside the image.
Best Practices
  • Treat the image as the single promotable artifact — build it once, then run that exact image in every environment, so "works on my machine" and "works in production" become the same claim about the same bytes.
  • Design every container to be disposable from day one — assume any instance can be killed and replaced at any moment, and keep no state inside it that you are not willing to lose.
  • Keep one concern per container — the app in one, the database in another — so each can be versioned, replaced, and scaled independently rather than entangled in a single image.
  • Declare every dependency the app needs inside the image, rather than relying on packages that happen to exist on the host, so the image is self-contained and reproducible.
Comparable tools Virtual machines isolation by booting a full guest OS — heavier, stronger boundary Ansible · Puppet shape a long-lived server instead of replacing it A JAR · a static binary · a venv package code but not the surrounding OS userland

Knowledge Check

What does packaging an application as a container image actually solve?

  • The app's runtime, libraries, and environment travel with the code as one artifact, so it runs identically everywhere
  • It makes the application independent of the host's kernel, so one Linux image can run directly on a Windows kernel
  • It compiles the application down to optimized machine code so it runs faster than the same program would natively
  • It sandboxes the application inside a hardware-enforced isolation boundary so that untrusted code running inside it can never affect the host

Why is the build-once-run-anywhere model the central value of containers?

  • The same image that passed tests is the exact artifact promoted to production, so verification and deployment concern identical bytes
  • The application and its dependencies are freshly re-installed from scratch on each machine so they match that host's environment
  • The image can be opened and edited in place on each individual host so it fits that particular environment's configuration needs
  • It lets you pack many more running copies of the application onto a single physical host than the very same workload would ever manage to fit when packaged as full VMs

What does "treat containers as cattle, not pets" mean in practice?

  • When a container misbehaves you replace it with a fresh one from the image rather than repairing it in place
  • You SSH into the running container and carefully patch it by hand so it keeps running without replacement
  • You give each container a stable name and identity and nurse that same instance along as long as you can
  • You store all of the application's important durable data inside the container's own filesystem so that it reliably survives every single restart

Why must durable application data not live inside the container's filesystem?

  • A container is disposable, so anything written inside it is lost when the container is replaced
  • The container's filesystem is mounted read-only at runtime and cannot be written to by any process
  • Writing data inside grows the image so large that it becomes too heavy to push to a remote registry
  • The host kernel enforces a rule that forbids containers from writing to their own filesystem

You got correct