LinuxLXXVIII · Containers from the Linux PerspectivePrimitives
Container primitives - namespaces, cgroups, OverlayFS, capabilities
What you'll learn
- Explain namespaces, cgroups, OverlayFS, capabilities
- Describe how containers are built from primitives
- Recognise the role of each primitive
- Apply the primitives for debugging
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
Containers are built on Linux primitives: namespaces for isolation, cgroups for resources, OverlayFS for layers, and capabilities for permissions. This lesson covers each primitive and how they work together.
Namespaces
Namespaces provide isolation: each namespace has its own view of a global resource.
- PID namespace: own set of PIDs. Container cannot see host processes.
- Network namespace: own network stack. Container has its own interfaces, routes, firewall.
- Mount namespace: own mount points. Container cannot see host filesystem.
- UTS namespace: own hostname and domain.
- IPC namespace: own IPC facilities.
- User namespace: own UID/GID mappings.
- Time namespace: own time (for clock skew).
A container is a collection of namespaces. The host sees the container as a process tree; the container sees itself as the only process.
cgroups
cgroups control resources: CPU, memory, I/O, network, processes.
A container has its own cgroup. The cgroup sets the limits:
- CPU quota: how much CPU time per period.
- Memory limit: maximum RSS.
- I/O weight: priority for I/O.
- Process count: maximum PIDs.
When the container exceeds a limit, the kernel enforces it (throttles, OOMs, etc.). cgroups v2 is the modern unified hierarchy.
OverlayFS
OverlayFS is a union filesystem: multiple layers stacked together.
There are four paths, matching the mount syntax:
mount -t overlay overlay \
-o lowerdir=/lower,upperdir=/upper,workdir=/work \
/merged
lowerdir: the read-only image layers, stacked.upperdir: the read-write layer where the container’s changes land.workdir: an empty scratch directory on the same filesystem asupperdir, which the kernel uses to stage copy-up and rename operations atomically.man 8 mount: “The workdir needs to be an empty directory on the same filesystem as upperdir.” It never holds anything you can use — do not go looking there for a container’s files.- the mount point (
/mergedabove): the merged view, and the thing the container sees as/.
When a container reads a file, OverlayFS checks the upper dir first, then the lower dir. When a container writes, the change goes to the upper dir.
This is how Docker images work: each layer is a diff; the container is the merged view.
Copy-up, whiteouts, and opaque directories
Three behaviours follow from that structure, and each one surprises somebody in production.
Copy-up. A file that exists only in lowerdir cannot be
modified in place, because the lower layers are read-only.
The first write copies the entire file to upperdir
first, then applies the change. Appending one line to a
10 GB log that came from the image consumes 10 GB in the
container layer, and the pause while the copy runs is real.
This is the mechanism behind “the container filled the disk
and I only wrote a few kilobytes”, and it is the reason
large mutable files belong on a volume rather than in an
image layer.
Whiteouts. Deleting a file that lives in lowerdir
cannot remove it either. OverlayFS records the deletion by
creating a character device with major and minor 0/0 at that
path in upperdir. The merged view then hides the file. The
consequence people care about: rm inside a container frees
no space — it adds a marker. A RUN rm -rf in a Dockerfile
that follows the RUN which created the files leaves both
layers in the image.
Opaque directories. When a whole directory is replaced
rather than merged, OverlayFS sets the
trusted.overlay.opaque="y" xattr on the upper directory,
which tells the kernel to stop looking at the lower layers
for that path.
Capabilities
Linux capabilities split root’s power into discrete pieces. A container runs with a restricted capability set:
- Allowed: CHOWN, SETUID, NET_BIND_SERVICE (for binding ports <1024).
- Denied: SYS_ADMIN, SYS_MODULE, raw I/O, mount, etc.
A container with no capabilities is a non-root user inside the kernel. Even if a process escapes the container, it cannot mount filesystems or load modules.
Putting it together
A container is:
- A process tree in its own PID namespace.
- A network stack in its own network namespace.
- A filesystem view in its own mount namespace.
- Resource limits in its own cgroup.
- A restricted capability set.
- An OverlayFS image as the root filesystem.
The host kernel enforces all of these. The container runtime (Docker, containerd) sets them up.
Debugging containers
For debugging, use the host tools to see inside the container’s namespace:
# Enter the container\'s namespace
nsenter -t <pid> -m -u -i -n -p -- /bin/bash
# View the container\'s filesystem
ls /proc/<pid>/root
# View the container\'s network
cat /proc/<pid>/net/tcp
Tools like nsenter, crictl, and ctr let you inspect containers without entering them.
Knowledge check
Knowledge check · 3 questions
Q1. What is the role of namespaces in containers?
Q2. By default, a container process running as UID 0 is UID 0 to the host kernel as well.
Q3. Which of the following are container primitives? Select all that apply.
Passing score: 75%. Answers are checked in this browser.