<aside> 📦

Module 16 — How Containers Work

This is the capstone. Every module before this taught a piece of Linux; a container is those pieces assembled into one idea — and the single most important sentence in modern DevOps is: a container is just a process. Not a tiny virtual machine, not a box, not magic — an ordinary Linux process that the kernel has been told to show a restricted view of the world (namespaces), cap the resources of (cgroups), and give its own root filesystem (an image). Understand that, and Docker, Kubernetes, and "works on my machine" all stop being mysteries. This module builds a container by hand from kernel primitives so you never have to take Docker on faith again.

Legend used throughout: 🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)

</aside>

<aside> 🚦

Before you start

This module composes the whole track. You need Module 8 — Processes (a container's main process is PID 1 — signals and zombies matter), Module 2 — The Filesystem (mounts and the root filesystem), Module 4 — Users, Groups, and Permissions (what "root" means — and how container-root differs), Module 13 — Networking (each container gets its own network stack), and the cgroups you met in Module 14 — Logs, Troubleshooting, and Performance. Tools: an Ubuntu 24.04 machine with util-linux (unshare, lsns — already present) and busybox-static for the rootfs demo; Docker optional. Every exercise here is safe — we create namespaces and cgroups that affect only our own throwaway processes.

Goes deeper: the kernel theory beneath this module — how namespaces and cgroups are actually implemented, and the isolation primitives (capabilities, seccomp) that make containers safe — are two full modules in the Operating Systems track: OS 12 (Namespaces, cgroups & Containers) and OS 13 (Isolation: Capabilities & seccomp). This module is the hands-on operator's build-it-yourself view; those are the mechanism.

</aside>

<aside> ℹ️

This page uses Mermaid diagram blocks. Notion shows them as code by default — click the block and set it to Preview to see the diagram. This reminder appears once per page.

</aside>

Part A — The big idea: a container is just a process

A1. What a container actually is — and how it differs from a VM

<aside> 📖

Official docs: What is Docker? (Docker Docs) · namespaces(7)

</aside>

🧠 A virtual machine virtualizes hardware: a hypervisor gives each VM its own emulated CPU, memory, and disks, and each VM boots a complete guest operating system with its own kernel. That's strong isolation, but it's heavy — a full OS per VM, gigabytes of RAM, tens of seconds to boot. A container virtualizes the operating system instead: it is a normal process running directly on the host's kernel, which the kernel has been asked to isolate. No guest kernel, no boot, no emulated hardware — a container "starts" as fast as launching a program, because launching a program is exactly what it is. This is why you can run hundreds of containers on a laptop but only a handful of VMs.

🧠 The kernel gives that process three restrictions, and those three are the container: namespaces control what it can see (its own process list, network, filesystem mounts, hostname), cgroups control what it can use (CPU, memory, PIDs), and an image provides the root filesystem it runs against. Docker, Podman, and containerd are conveniences that assemble these kernel features and ship the image — but the isolation itself is done by the Linux kernel, not by Docker.

<aside> 🧠

Counter-intuitive: from the kernel's point of view there is no "inside the container" — the isolation is an illusion projected at the process, not a wall built around it. People picture a container as a sealed box the process lives in. But run ps on the host and you'll see the container's processes sitting right there as ordinary PIDs, owned by an ordinary UID, schedulable like any other. The kernel is running one shared process table; the container's process simply has a namespaced view that hides everything outside its group and renumbers what's left. Nothing "contains" it — the kernel just answers its questions selectively. This is the whole reason containers are lightweight (no second kernel to lie to it) and the whole reason they're less isolated than VMs (one shared kernel is the only thing standing between a container and the host — Part D).

</aside>

<aside> 🧩

Real-world analogy — a one-way-mirror interview room

A suspect in an interview room believes the room is the whole world: four walls, one table, no windows. In reality they're in a normal room inside a normal building, and the investigators on the other side of the mirror can see them perfectly, walk in any time, and are using the same air conditioning and power as every other room. The suspect's restricted view is the isolation — not a fortress, just controlled information. From the outside, it's an ordinary room among many.

Where the analogy stops working. The suspect and the investigators still breathe the same air through the same vents — and that shared infrastructure is precisely the container's weakness: they share the host kernel, so a flaw in that shared kernel lets the "suspect" affect the whole building (a container escape), which a true VM's separate foundation would prevent. Also, the room's walls are physical and fixed, whereas a container's "walls" are just the kernel choosing what to reveal, reconfigurable in an instant with a syscall. The analogy captures the view-based isolation but understates how thin — and how software-defined — the boundary really is.

</aside>

🧪 Exercise A1.1 — Every process already has namespaces (read-only, safe anywhere)

ls -l /proc/self/ns/          # the namespaces THIS shell belongs to — you're already in a full set
# a "container" just means a process whose namespaces differ from the host's; yours match the host's

A2. The three primitives — namespaces, cgroups, and the rootfs

<aside> 📖

Official docs: namespaces(7) · cgroups(7)

</aside>

🧠 A container is the sum of exactly three kernel mechanisms — memorise this trinity and everything else follows:

🧠 Namespaces = what it can SEE (isolation). Separate views of processes, network, mounts, hostname, users. cgroups = what it can USE (limits & accounting). Caps on CPU, memory, process count, I/O. The root filesystem = what it runs against. An image unpacked into a directory that becomes the container's / via chroot/pivot_root. Give a process new namespaces + a cgroup + a rootfs and exec it — that is a container, with or without Docker.

<aside> 📦

Diagram — the anatomy of a container (set this block to Preview). One shared kernel; each container is a process with its own view, limits, and filesystem.

flowchart TD
    K["Host Linux kernel (ONE, shared by all)"]
    K --- H["Host processes<br>(host namespaces)"]
    K --- C1["Container A = a process"]
    K --- C2["Container B = a process"]
    C1 --- N1["namespaces: what it SEES<br>(pid/net/mnt/uts/...)"]
    C1 --- G1["cgroup: what it USES<br>(cpu/mem/pids caps)"]
    C1 --- R1["rootfs: image layers<br>as its /"]
    C2 --- N2["namespaces: own view"]
    C2 --- G2["cgroup: own limits"]
    C2 --- R2["rootfs: own image"]

</aside>

🧪 Exercise A2.1 — Make your own UTS namespace and change its hostname (safe — the change exists only inside the new namespace)