Linux Containers Under the Hood: Namespaces, Cgroups, and OverlayFS

Containers are not virtual machines; they are standard Linux processes isolated by kernel Namespaces and resource-governed by Control Groups (cgroups) running on top of union filesystems (OverlayFS). This guide explores the low-level kernel building blocks that power container engines like Docker, Podman, and containerd.


⚡ Quick Dive

Container Primitives Cheat Sheet

Primitive Kernel Mechanism What It Isolates / Controls CLI Diagnostic
PID Namespace CLONE_NEWPID Process IDs (container has its own PID 1) lsns -t pid
NET Namespace CLONE_NEWNET Network interfaces, routes, firewall rules ip netns list
MNT Namespace CLONE_NEWNS Filesystem mount points and root trees lsns -t mnt
UTS Namespace CLONE_NEWUTS Hostname and NIS domain name lsns -t uts
IPC Namespace CLONE_NEWIPC System V IPC, POSIX message queues lsns -t ipc
USER Namespace CLONE_NEWUSER Maps container root (UID 0) to unprivileged host UID lsns -t user
Cgroups (v2) /sys/fs/cgroup/ CPU, Memory, I/O, PIDs hard resource quotas systemd-cgls
OverlayFS overlay driver Multi-layered Copy-on-Write union filesystem mount | grep overlay

Creating a Container by Hand with Standard Linux Utilities

# 1. Download Alpine Linux rootfs tarball and extract
mkdir -p /tmp/mycontainer/rootfs
curl -sL https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-minirootfs-3.20.0-x86_64.tar.gz | tar -xz -C /tmp/mycontainer/rootfs

# 2. Launch an isolated shell with new PID, UTS, Mount, and Network namespaces
sudo unshare --pid --uts --mount --net --fork /bin/sh -c "
    hostname container-box
    mount -t proc proc /tmp/mycontainer/rootfs/proc
    chroot /tmp/mycontainer/rootfs /bin/sh
"

📖 Extended Guide

1. The 7 Linux Kernel Namespaces

Namespaces partition kernel resources so that one set of processes sees one set of resources while another set of processes sees a completely different set.

+-------------------------------------------------------------------------+
|                              Linux Kernel                               |
+------------------------------------+------------------------------------+
|        Host Namespace              |        Container Namespace         |
|  - Hostname: host.production       |  - Hostname: web-app-7d8           |
|  - IP: 192.168.1.100               |  - IP: 172.17.0.2                  |
|  - Process List: 1...2500          |  - Process List: 1, 2, 3           |
|  - Root FS: /                      |  - Root FS: /var/lib/docker/...    |
+------------------------------------+------------------------------------+

Inspecting Namespaces on a Live System

# List all active namespaces across all processes
sudo lsns

# Inspect namespaces attached to a specific process PID
ls -l /proc/<PID>/ns/

Entering Existing Namespaces with nsenter

Troubleshoot container issues directly without a shell running inside the container:

# Enter the network namespace of process <PID>
sudo nsenter -t <PID> -n ip a

# Enter all namespaces of a target container process
sudo nsenter -t <PID> --all /bin/bash

2. Control Groups (cgroups v2): Resource Limitation

While namespaces control what a process can see, Control Groups (cgroups) control how many resources a process can use.

In modern Linux (cgroups v2 unified hierarchy), limits are managed via the pseudo-filesystem under /sys/fs/cgroup/:

/sys/fs/cgroup/
├── cgroup.controllers
├── memory.max
├── cpu.max
└── system.slice / docker.slice

Testing Cgroup Limits Manually:

# 1. Create a new cgroup named 'sandbox'
sudo mkdir /sys/fs/cgroup/sandbox

# 2. Limit memory to 256MB
echo "268435456" | sudo tee /sys/fs/cgroup/sandbox/memory.max

# 3. Limit CPU to 50% of one core (50000 microseconds out of 100000 period)
echo "50000 100000" | sudo tee /sys/fs/cgroup/sandbox/cpu.max

# 4. Attach a process (e.g. active shell) to the cgroup
echo $$ | sudo tee /sys/fs/cgroup/sandbox/cgroup.procs

3. OverlayFS: Layered Copy-on-Write Storage

Container images use OverlayFS to stack multiple read-only image layers under a single top writable container layer:

Merged View (Container Mount Point: /mnt/merged)
  ▲
  ├── Upper Layer (Writable Layer: /tmp/overlay/upper)
  └── Lower Layers (Read-Only Base Image: /tmp/overlay/lower1, /tmp/overlay/lower2)

Mounting an OverlayFS Pipeline Manually:

mkdir -p /tmp/overlay/{lower,upper,work,merged}
echo "Base Image Content" > /tmp/overlay/lower/base.txt

# Mount the union filesystem
sudo mount -t overlay overlay \
  -o lowerdir=/tmp/overlay/lower,upperdir=/tmp/overlay/upper,workdir=/tmp/overlay/work \
  /tmp/overlay/merged

# Inspect merged directory
ls -la /tmp/overlay/merged
# Any modifications made inside /tmp/overlay/merged write only to /tmp/overlay/upper

4. Rootless Containers & User Namespaces

In traditional Docker, the daemon runs as root. If a container escapes its boundaries, it gains root privileges on the host.

User Namespaces (CLONE_NEWUSER) map UID 0 (root inside container) to an unprivileged UID on the host (e.g., UID 100000 via /etc/subuid and /etc/subgid):

  • Inside Container: whoami -> root (UID 0)
  • On Host Kernel: Process runs strictly as unprivileged user 100000.

This architecture enables tools like Podman and rootless Docker to run securely without root daemon privileges.