AnyLearn
All lessons
Programmingintermediate

A Container Is Not a Thing

There is no container object in the Linux kernel, no container system call, and no container data structure. A container is an ordinary process started with several isolation features switched on at once. This lesson establishes what those features are, what the word actually names, and why the distinction changes how you reason about them.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 8

The kernel has no idea what a container is

Search the Linux kernel for a container and you will not find one. There is no container struct, no create_container() system call, and no subsystem that owns the concept.

What exists is an ordinary process, started with several independent isolation features enabled. The word container names that combination, and it is a term of art in userspace rather than a kernel object.

That is not pedantry, and it has practical force. Because a container is just a process, ps on the host shows it, the host scheduler schedules it, and it competes for the same CPU and memory as everything else. There is no boundary in the kernel separating container processes from ordinary ones; there are only processes with different views and different limits.

The lesson Syscalls and the Kernel Boundary builds the two features this rests on and shows that a container falls out of them. This cursus starts from that point and asks the questions it leaves open: where does the filesystem come from, what actually starts it, and how much isolation is really there.

A one-paragraph recap first, so the rest stands alone.

Full lesson text

All 8 steps on one page, for reading, reference, and search.

Show

1. The kernel has no idea what a container is

Search the Linux kernel for a container and you will not find one. There is no container struct, no create_container() system call, and no subsystem that owns the concept.

What exists is an ordinary process, started with several independent isolation features enabled. The word container names that combination, and it is a term of art in userspace rather than a kernel object.

That is not pedantry, and it has practical force. Because a container is just a process, ps on the host shows it, the host scheduler schedules it, and it competes for the same CPU and memory as everything else. There is no boundary in the kernel separating container processes from ordinary ones; there are only processes with different views and different limits.

The lesson Syscalls and the Kernel Boundary builds the two features this rests on and shows that a container falls out of them. This cursus starts from that point and asks the questions it leaves open: where does the filesystem come from, what actually starts it, and how much isolation is really there.

A one-paragraph recap first, so the rest stands alone.

2. Namespaces change what a process can see

A namespace wraps a global kernel resource so that processes inside it see their own instance of it. Same kernel, different view.

There are several, and each virtualises one thing.

PID. Processes inside see their own numbering, starting at 1. The process that is PID 1 inside has some other PID on the host, and cannot see host processes at all.

Mount. Its own filesystem tree, which is where the container's apparent root directory comes from.

Network. Its own interfaces, routing table, firewall rules and port space, which is why two containers can both bind port 80.

UTS. Its own hostname.

IPC. Its own shared memory and semaphores.

User. Its own user and group ID mapping, so root inside can map to an unprivileged user outside.

Cgroup. Its own view of the cgroup hierarchy.

They are independent. A process can join some and not others, and that composability is used constantly: a debugging container commonly shares the network namespace of another so it can inspect its traffic while keeping its own filesystem.

3. Cgroups change what a process can use

Namespaces control visibility. They do nothing about consumption: a process in its own namespaces can still use every core and all the memory.

Control groups supply the other half. A cgroup is a set of processes with limits and accounting attached, arranged in a hierarchy, with controllers for each resource.

CPU. A share of time relative to other groups, or a hard ceiling.

Memory. A maximum, and exceeding it triggers the out-of-memory killer within that group rather than across the host.

I/O. Bandwidth and operations per second against block devices.

PIDs. A cap on how many processes the group may create, which is the defence against a fork bomb taking down the host.

The pairing is what makes the whole thing useful, and it is worth stating as a single sentence: namespaces make a process unable to see the rest of the system; cgroups make it unable to consume the rest of the system. Either alone is insufficient. Isolation without limits lets one workload starve the others; limits without isolation lets it read their processes and files.

The memory row also explains a common production confusion: a container killed for exceeding its limit shows as an OOM kill even though the host had memory free, because the limit that was hit was the group's.

4. What actually happens on start

That sequence is the whole of container creation, and every step is an existing Linux facility that predates containers by years.

Namespaces arrived incrementally through the 2000s. Cgroups arrived from work at Google. chroot, the ancestor of the filesystem step, dates to the 1970s.

So containers were not invented as a technology. They were assembled from primitives that already existed, and the invention was the packaging: a format for the filesystem, a tool to run the sequence, and a registry to distribute it.

That is why the interesting questions in this cursus are not about the kernel. The kernel part is the diagram above and the lesson that precedes this one.

The questions that remain are where step 5 gets its filesystem, who runs the sequence, how the network namespace in step 2 reaches anything, and how much the constraints in step 6 are actually worth. Those are the next three lessons.

flowchart TD
A["Start a container"] --> B["clone() with namespace flags"]
B --> C["New process, isolated views"]
C --> D["Put it in a cgroup with limits"]
D --> E["pivot_root to the image filesystem"]
E --> F["Drop capabilities, apply seccomp"]
F --> G["exec the entrypoint"]
G --> H["An ordinary process, constrained"]

5. The difference from a virtual machine

The comparison is made constantly and usually stated as a size difference, which misses what matters.

A virtual machine runs a full guest kernel on virtual hardware provided by a hypervisor. The guest has its own scheduler, its own memory manager, its own drivers.

A container shares the host kernel. There is one kernel, and every container's system calls are handled by it.

Everything else follows from that single fact.

Startup is milliseconds rather than seconds, because there is no kernel to boot, only a process to start. Overhead is a process rather than a machine. Density is correspondingly higher.

And the isolation is weaker in kind, not merely in degree. A VM escape requires defeating the hypervisor, a small interface. A container escape requires defeating the kernel's isolation, and the kernel's system call interface is enormous. That is a much larger attack surface, and it is shared by every container on the host.

The consequence people most often trip over: you cannot run a different kernel. Linux containers need a Linux kernel, which is why Docker on macOS or Windows runs a Linux virtual machine and puts the containers inside it. The lightness is real and it is lightness relative to a VM you may still be running underneath.

6. What the isolation actually leaks

Sharing a kernel means sharing everything the kernel does not namespace, and several of those matter in practice.

The clock. There is no time namespace in wide use for the wall clock, so changing the time inside a container is changing it for the host.

Kernel parameters. Most sysctl settings are global. A container cannot tune the network stack without affecting everyone.

Kernel modules. Loading one affects the host. Containers cannot have their own.

The kernel version. Every container sees the host's, which is why a container built against a newer kernel's features fails on an older host despite the image being self-contained in every other respect.

Resource views, historically. Tools inside a container reading /proc/cpuinfo or /proc/meminfo classically saw the host's values rather than the cgroup's limits, so runtimes that size thread pools or heaps from apparent CPU and memory would over-allocate badly. Language runtimes have since become cgroup-aware, and this remains a live source of misconfiguration in older stacks.

That last one is the practical lesson of the whole model. The isolation is a set of specific features, each covering one thing, and anything nobody wrote a namespace for is simply shared.

7. PID 1 is a real job

The PID namespace creates a subtlety that causes a specific and common production problem.

On Linux, PID 1 has two responsibilities beyond running. It must reap zombies: when any process is orphaned, it is reparented to PID 1, which must call wait() to release its entry. And it has different signal semantics: signals without an explicit handler are not applied by default, which is the opposite of every other process.

Inside a container, your application is PID 1 and almost certainly does neither.

Two failures follow.

Zombie accumulation. An application that spawns child processes and does not reap orphans leaks process table entries until the PID limit is reached.

Ignored shutdown signals. The runtime sends SIGTERM to request a graceful stop. An application that installed no handler does not receive the default terminate behaviour, so nothing happens, and after a timeout it is killed with SIGKILL. The symptom is every deploy taking ten seconds longer than it should and connections being dropped rather than drained.

The fix is either handling signals explicitly in the application, or running a tiny init process as PID 1 that reaps children and forwards signals. Most runtimes offer a flag for exactly this, and it is worth using by default.

8. What the word names

ComponentProvidesKernel feature
Isolated process viewOwn PIDs, hostname, IPCPID, UTS, IPC namespaces
Isolated filesystemOwn root directoryMount namespace, pivot_root
Isolated networkOwn interfaces and portsNetwork namespace
Resource limitsCPU, memory, I/O, process capscgroups
Reduced privilegeFewer capabilities, filtered syscallscapabilities, seccomp
Portable filesystemThe imageNot a kernel feature at all

The last row is the one that matters most for the rest of this path. Everything above it is the kernel, and the kernel gives you isolation for a process whose files are already on the machine.

What it does not give you is a way to package those files, name them, verify them, or move them between machines. chroot existed for decades and did not produce a container ecosystem, because a directory tree is not something you can ship.

So the technology that changed practice was not the isolation. It was the image format and the registry that distributes it, which turned a filesystem into an artefact you can build once, address by digest, and run anywhere.

That is the next lesson, and it turns out to reuse an idea from a completely different tool.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. What is a container, from the kernel's point of view?
    • An ordinary process started with several isolation features enabled
    • A kernel object created by a dedicated system call
    • A lightweight virtual machine sharing the host's hardware
    • A chroot jail with additional filesystem permissions
  2. What is the division of labour between namespaces and cgroups?
    • Namespaces limit consumption; cgroups limit visibility
    • Namespaces limit visibility; cgroups limit consumption
    • Both limit visibility, at different levels of the hierarchy
    • Namespaces apply to processes; cgroups apply to filesystems
  3. Why is container isolation weaker in kind than a virtual machine's?
    • Because containers cannot use hardware virtualisation extensions
    • Because container images are smaller and carry fewer security patches
    • Because escaping requires defeating the kernel's isolation, and the syscall interface is a far larger attack surface than a hypervisor
    • Because containers run as root by default
  4. Why does a container often ignore SIGTERM and take the full timeout to stop?
    • Because the runtime sends the signal to the wrong namespace
    • Because cgroups block signal delivery until limits are released
    • Because the mount namespace prevents signal handlers from loading
    • Because the app is PID 1, and PID 1 does not get default signal behaviour without an explicit handler
  5. Why did chroot, which existed for decades, not produce a container ecosystem?
    • It provided no way to package, name, verify or ship a filesystem as an artefact
    • It offered no process isolation whatsoever
    • It required root privileges that were unavailable at the time
    • It could not be combined with resource limits

Related lessons

Programming
advanced

The Runtime Stack, and What Isolation Is Worth

One command hides four layers of software and a set of standards that made them interchangeable. This lesson takes the stack apart, then asks the question the whole path has been building toward: given a shared kernel, how much is container isolation actually worth, and what has to be added before it is a security boundary.

8 steps·~12 min
Programming
advanced

Networking and Storage: Connecting an Isolated Thing

Isolation is the point, and a container that can reach nothing and keep nothing is useless. This lesson covers how a network namespace is wired to the outside, why published ports and container-to-container traffic work completely differently, and where data has to live given that the writable layer disappears.

8 steps·~12 min
Programming
advanced

Images: A Filesystem You Can Address by Hash

The kernel gives isolation for files already on the machine. The image format is what turned a directory tree into something you can build once, name by digest and run anywhere. This lesson covers layers and the union filesystem that stacks them, why the build cache behaves as it does, and the content addressing that makes it all work.

8 steps·~12 min
Programming
advanced

Scheduling and Resources: Requests Are Not Limits

Two numbers govern where a pod lands and how it behaves under pressure, and they do completely different jobs. Requests are used for placement and are a promise; limits are enforced at run time and are a ceiling. This lesson separates them, covers the asymmetry between CPU and memory enforcement, and explains what actually happens when a node runs out.

8 steps·~12 min