Chapter 2
Anatomy of a container
A container is a process wearing five kernel features. Meet namespaces, cgroups, capabilities, seccomp, and the LSMs, plus OverlayFS, the layer trick behind images.
Here is the thing nobody tells you first: there is no container object in the Linux kernel. You cannot open one, list one, or point at one. A container is a normal process, the same kind of process as your text editor, with five separate kernel features wrapped around it. Docker and the other runtimes assemble those five for you and give the result a friendly name, but the kernel only ever sees a process.
What the process can SEE, its own PID, mount, network, user views.
Where its filesystem COMES FROM, image layers union-mounted; the default of several storage drivers.
What the process can USE, CPU, memory, PIDs, I/O ceilings.
Which root powers it keeps, drop CAP_SYS_ADMIN and most escapes die.
Which syscalls it may CALL, the verb filter.
Which objects it may TOUCH, the LSM policy layer.
OCI Containers
So if the kernel has never heard of containers, who decides what one is? Since June 2015 that job has belonged to the Open Container Initiative (OCI), a Linux Foundation project that pins the word down on paper: a runtime spec for how to execute a container from a filesystem bundle, an image spec for the layered images you are about to meet, and a distribution spec for shipping them around. runc, maintained under the initiative's own roof, is the program that actually assembles the five features below, and Docker, Podman, and containerd all drive an OCI runtime underneath. When this site's isolation tier says container, this is the membership test it means.
Those five features answer five different questions. It is worth taking them one at a time, because the differences between them are exactly what chapters 3 and 4 turn on.
Linux namespaces, what it can see
A namespace gives a process a private view of one global kernel resource. There are several, mount, PID, network, user, UTS (hostname), IPC. Put a process in its own mount namespace and it sees a filesystem you assembled for it; its own PID namespace and it believes it is PID 1 with no siblings; its own network namespace and it gets its own interfaces and routing table. The one that changes the security picture is the user namespace: it maps user IDs so a process can be root inside the namespace while being an unprivileged user outside it. That mapping is what makes rootless containers possible.
OverlayFS, where its filesystem comes from
A container image is just a stack of read-only directories, one per layer. OverlayFS mounts that stack as a single view with one thin writable layer on top: reads fall through to the first layer that has the file, the first write copies the file up into the writable layer, and deletes leave whiteout markers. Start a hundred containers from one image and they all share the same read-only layers; each pays only for what it changes. The runtime builds this mount inside the container's mount namespace and pivots the process's root into it. OverlayFS is the default storage driver on most Linux setups, engines can swap in others (btrfs, ZFS), but the layered model is the same. One caution: OverlayFS is plumbing. It decides what the filesystem looks like; the five features around it decide what the process may do.
cgroups v2, how much it can consume
Where namespaces control what a process can see, control groups control how much it can consume, CPU time, memory, PID count, block I/O, as hard ceilings. cgroups are not a security boundary on their own; they will not stop anyone from reading a file they can already reach. What they stop is exhaustion: a fork bomb or a runaway memory leak hits its cgroup limit and dies, instead of dragging the whole host down with it.
Linux capabilities, which root powers it keeps
Root in a container is still root on the kernel, the same UID 0, the same syscalls. What keeps that from being a free escape is that the runtime strips most of root's powers before your process ever starts. Linux capabilities split root's monolithic authority into roughly forty discrete privileges, load a kernel module, change the clock, mount a filesystem, that can each be granted or dropped. The runtime keeps the handful a normal program needs and drops the rest. CAP_SYS_ADMIN is the one that matters most: it is so broad it is nearly equivalent to full root, so keeping it, or running --privileged, which keeps everything, reopens the very escape the drops were closing.
seccomp-BPF, which syscalls it may call
seccomp-BPF filters which syscalls the process is even allowed to make. It sits at the syscall entry point, where the kernel has only the syscall number and the raw register arguments, scalar values, and pointers as bare addresses. That lets it ban a whole verb outright: this process may never call ptrace, or mount, or bpf. What it cannot do is inspect what a pointer points to. When a process calls open() with a path, seccomp sees an address, not the string, and by design refuses to dereference it, because a value it trusted could be swapped out from under it after the check. So seccomp can decide which syscall, never which file.
Linux Security Modules (LSM), which objects it may touch
That last gap, deciding on the actual object rather than the verb, is what a Linux Security Modules (LSM) does. An LSM is a framework of hook points buried deep in the kernel, one at every security-relevant decision, and they fire after the path has been resolved to a real . At that point the kernel knows exactly which file, which socket, which process is about to be touched, and it asks the module: allowed? AppArmor and SELinux are the two classic LSMs that container runtimes ship, AppArmor with per-application, path-based profiles; SELinux with a comprehensive label-based policy, both authored by an administrator and applied system-wide. Because they act on the resolved object, they can express what seccomp cannot: "may read /etc, may write only under /var/lib/app."
Verbs and nouns
That contrast is the whole point, and it is worth stating flatly: seccomp filters verbs; LSMs filter nouns. seccomp reasons about the syscall, the request at the door, and cannot see the object it resolves to. An LSM reasons about the resolved object, the actual file or socket, and does not care which syscall got there. Neither replaces the other, and the standard recipe uses both: seccomp bans the dangerous verbs outright, the LSM scopes which nouns the surviving verbs may reach. They compose.
Remember what these are
Now step back and notice what just happened. A container dissolved into a process plus Linux namespaces, cgroups v2, Linux capabilities, seccomp-BPF, and an LSM. Every one of the five is a general-purpose kernel feature that predates the word container and turns up in plenty of places containers never touch. Hold onto that. In chapter 4 we will watch the exact same five primitives show up again, building the second wall around the one thing a container is supposed to be the opposite of.