PHASE 3 — Containerization: Docker and Kubernetes
Everything from here builds directly on Phases 1 and 2. A container is not a new kind of virtual computer — it's an ordinary Linux process, given its own view of the filesystem, its own process tree, its own network stack, and a capped slice of resources, using EXACTLY the OS mechanisms you already know: namespaces, cgroups, and the filesystem model. If a container ever does something confusing, the answer is almost always "go back to Phase 1's mental model and apply it here."
Going further: this phase gets you to "I understand containers and Kubernetes". Docker & Kubernetes — The Real-Life Field Guide continues to production: hardened images, probes and graceful shutdown, autoscaling, OpenTelemetry observability, GitOps, canary releases and disaster recovery, built as one system in a Linux lab VM.
Part 14 — Container fundamentals
Every part so far was one OS running many processes with the illusion of isolation (Part 1's lie, again). A container is that same illusion, turned into a deliberate, hardened feature. This part shows that namespaces and cgroups are the real mechanism behind it — not magic, and nothing you haven't already learned the pieces of.
Chapter 44 — What a container actually is (namespaces + cgroups + a filesystem)
In one sentence
A container is a normal Linux process running with its own PID namespace (so it sees only its own processes), its own network namespace (Chapter 37's exact model, isolated), a cgroup capping its resource usage (Chapter 43), and its own root filesystem — no new kernel feature invented specifically for "containers" at all.
Why this matters
"How is a container different from a process" and "why did my container get OOM-killed" both stop being mysterious the moment you see containers as a COMPOSITION of things you already learned in Phase 1, not a separate technology.
How it actually works
FOUR INGREDIENTS, each one YOU ALREADY KNOW:
1. PID NAMESPACE the container's main process becomes PID 1
INSIDE its own namespace (Chapter 6's process
tree, but the container gets its OWN, private
root of that tree) -- it cannot see, and
cannot signal (Chapter 20), processes outside
its namespace.
2. NETWORK NAMESPACE its own network interfaces, IP address, and
routing table (Chapter 37) -- including its
own, SEPARATE `localhost`, exactly as Chapter
37's real-world example previewed.
3. MOUNT NAMESPACE / ROOT FILESYSTEM its own view of the
filesystem (Chapter 24's hierarchy), typically
built from a container IMAGE (Chapter 46) --
so it can have its OWN `/etc`, its own
installed packages, entirely separate from the
host's, even though it's ultimately running on
the SAME kernel.
4. CGROUP a resource limit (CPU, memory -- Chapters 8,
10, 13, 43) applied to the container's
process(es) as a group, enforced by the
kernel, not politely requested.
PUT TOGETHER: a container process, from the KERNEL'S perspective, is
still just an ordinary process -- `ps aux` on the HOST machine shows
it, right alongside every other process, with its own real PID in the
host's OWN, top-level PID namespace. It's just been given a
deliberately RESTRICTED VIEW of the system (can't see other
namespaces' processes/network) and a CAPPED SLICE of resources
(cgroups) -- fundamentally the same isolation idea as Chapter 1's
"the OS as a resource manager" and Chapter 10's virtual memory, just
composed at a coarser, whole-process-environment level.
Real-world example
Docker (first publicly introduced by Solomon Hykes at a PyCon
lightning talk in 2013) is often, inaccurately, credited with
"inventing containers" -- the real, well-documented history is more
interesting and more directly illustrates this chapter's point: the
underlying Linux kernel mechanisms Docker relies on -- namespaces
(introduced incrementally into the kernel starting in the early-to-
mid 2000s) and cgroups (merged into the mainline kernel in 2007,
developed originally at Google for exactly this resource-isolation
purpose) -- PREDATED Docker by years. LXC (Linux Containers), an
earlier project, already used these SAME primitives directly to
provide container-like isolation before Docker existed.
What Docker actually, genuinely contributed -- and this is well
documented across its own project history and countless retrospective
engineering writeups -- was a dramatically better USER EXPERIENCE: a
simple, portable IMAGE FORMAT (Chapter 46) that could be built once
and run identically anywhere, a straightforward CLI, and a
registry/distribution model (Chapter 52) for sharing images. Docker's
genuine innovation was PACKAGING AND ERGONOMICS around existing
kernel primitives, not the isolation mechanism itself -- which is
exactly why this chapter can teach you "what a container is" using
ONLY concepts from Phase 1: the primitives really are that
fundamental, and really did exist before the word "container" became
an industry-wide household term.
Try it yourself
# See a container process from the HOST's perspective -- it's just a
# normal process (requires Docker -- if not installed yet, install it
# now: get.docker.com has the official install script, or use your
# distro's package manager)
docker run -d --name pidtest nginx
docker inspect --format '{{.State.Pid}}' pidtest # the REAL, host-
# level PID
ps -p $(docker inspect --format '{{.State.Pid}}' pidtest) # there it
# is, in
# your
# NORMAL
# host `ps`
# output
# See the PID namespace difference: PID 1 inside vs. outside
docker exec pidtest ps aux # inside the container, nginx is PID 1
ps -p $(docker inspect --format '{{.State.Pid}}' pidtest) # outside,
# it's some
# other,
# larger PID
# See the cgroup this container was actually assigned
docker inspect --format '{{.HostConfig.Memory}}' pidtest
cat /sys/fs/cgroup/system.slice/docker-*.scope/memory.max 2>/dev/null | head -1 || \
echo "(cgroup path varies by cgroup version/driver -- informational)"
docker rm -f pidtest
Build it in Go
The best way to stop thinking of containers as magic is to build one.
Chapter 80 does it in about 150 lines of Go: new UTS,
PID, mount, IPC, and network namespaces; chroot into a BusyBox filesystem;
a private /proc; and a cgroup v2 limit that stops a fork bomb at 20
processes. For how attackers break out of containers, and how to stop them,
see Security Engineering in Depth, Chapter 15.
Common mistakes
- Thinking a container is "a lightweight VM." Chapter 45 makes this precise, but the short version: there's no virtualised hardware, no separate kernel — it's a specially-isolated ordinary process, sharing the HOST's kernel directly.
- Assuming "container" is a Linux kernel feature you can point to.
There's no
containersyscall or kernel object — it's a COMPOSITION of namespaces, cgroups, and a filesystem, orchestrated by tooling (Docker, containerd, etc.), exactly as this chapter describes. - Forgetting the container shares the HOST's kernel. A kernel-level vulnerability or a container escape (a real, documented risk category, covered in this wiki's security guides) can cross this isolation boundary — it's strong, but it is not the same guarantee as separate hardware virtualization.
Check yourself
- Name the four Linux mechanisms that, composed together, make a container.
- Why does
ps auxon the HOST show a container's processes too? - What did Docker actually invent, versus what already existed in the Linux kernel before it?
(Answers: Appendix E.)
Further reading
- Talk: Solomon Hykes' original 2013 Docker introduction (search "Docker PyCon 2013 lightning talk") — genuinely worth watching for the real history.
- Reference:
man 7 namespaces,man 7 cgroups,man 7 pid_namespaces. - Article: search "containers are just Linux processes" — this exact framing has been written up excellently, multiple times, by various container-runtime engineers.