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
intermediate

Canary Releases: Deciding With Evidence Instead of Nerve

A canary release sends a slice of real traffic to a new version and asks whether it is healthy. This lesson covers what to measure, why comparing the canary against the current version beats comparing against history, the statistics problem that makes small canaries weak evidence, and how automated promotion and rollback turn a judgement call into a rule.

7 steps·~11 min
Programming
intermediate

Why Deploys Break Things, and the Strategies That Answer It

Deploying is the moment a working system is replaced by a different one while people are using it. This lesson covers what actually goes wrong at that moment, the research finding that shipping fast and shipping safely are not opposites, and the four deployment strategies as answers to one question: how many users meet a bad version before you find out.

7 steps·~11 min
Programming
intermediate

Making Rollback Possible: The Changes That Cannot Be Undone

Every deployment strategy assumes you can go back, and that assumption is the one most often false when it matters. This lesson covers what actually makes a rollback work, the database migration pattern that keeps schema changes reversible, the one-way doors that no amount of tooling can undo, and how to tell which kind of change you are about to ship.

7 steps·~11 min
Programming
intermediate

Feature Flags: Separating Deploy From Release

A feature flag turns shipping code and exposing behaviour into two independent decisions, which is what lets a team deploy continuously while releasing on someone else's schedule. This lesson covers the four kinds of flag and their very different lifespans, the debt they accumulate, and the discipline that keeps a flag system from becoming untestable.

7 steps·~11 min