DEV Community

Michael Wolfenberger
Michael Wolfenberger

Posted on

Docker Under the Hood: Namespaces, Cgroups, and Layered Filesystems Explained

The Virtual Machine Misconception

Many engineers approach Docker thinking it operates like a lightweight virtual machine. It does not.

A virtual machine runs a hypervisor that virtualizes physical hardware (CPUs, memory controllers, network adapters). Inside that simulated hardware lives a full guest operating system with its own independent kernel, init system, device drivers, and system background services.

+-----------------------------+       +-----------------------------+
|      Virtual Machine        |       |          Container          |
+-----------------------------+       +-----------------------------+
| App A       | App B         |       | App A       | App B         |
| Bin/Libs    | Bin/Libs      |       | Bin/Libs    | Bin/Libs      |
| Guest OS (Full Kernel)      |       | Root Filesystem (No Kernel) |
| Hypervisor (Hardware Sim)   |       | Linux Namespaces + Cgroups  |
+-----------------------------+       +-----------------------------+
| Host OS (Linux/Darwin/NT)   |       | Host Linux Kernel           |
| Bare Metal Hardware         |       | Bare Metal Hardware         |
+-----------------------------+       +-----------------------------+
Enter fullscreen mode Exit fullscreen mode

A Docker container executes directly on the host Linux kernel. When you run docker run -d nginx, the Nginx worker is an ordinary process visible in the host machine’s process tree. The difference is that the kernel restricts what that process can see, what resources it can consume, and what files it can modify.


1. Restricting Visibility: Linux Namespaces

Linux namespaces govern boundary isolation by restricting what system resources a process can observe.

Docker relies on six primary namespaces:

  • pid (Process IDs): Assigns an isolated process hierarchy. Inside the container, your main application process appears as PID 1. On the host, it runs under an ordinary host PID (e.g., PID 14382).
  • net (Networking): Provides dedicated virtual network interfaces, independent routing tables, and isolated firewall/iptables rules.
  • mnt (Mount points): Isolates the filesystem mount table so the process only sees its own root directory (/), completely separated from the host storage tree.
  • ipc (Inter-Process Communication): Prevents containers from accessing shared memory segments or message queues belonging to other host processes.
  • uts (UNIX Timesharing System): Enables each container to define its own hostname and domain name independently of the host.
  • user (User IDs): Maps an unprivileged user inside the container to a distinct UID on the host (e.g., container root mapped to an unprivileged UID on the host system).

Testing Namespace Isolation Manually

You do not need Docker to spin up an isolated process environment. The native Linux unshare utility creates namespaces directly from the terminal:

# Spawn a new shell inside an isolated PID and mount namespace
sudo unshare --fork --pid --mount-proc /bin/bash

# View running processes from within the new namespace
ps aux
Enter fullscreen mode Exit fullscreen mode

Inside that shell, ps aux will only show two processes: the /bin/bash shell running as PID 1 and the ps command itself. The rest of the host system's running processes are completely invisible.


2. Metering Consumption: Control Groups (cgroups v2)

If namespaces dictate what a process can see, control groups (cgroups) dictate what a process can consume.

Without resource boundaries, a runaway container could exhaust available system memory, causing the Linux Out-Of-Memory (OOM) killer to terminate critical host services, or consume 100% of CPU time through unbounded loops.

In modern Linux distributions using cgroup v2, resource constraints are configured through a unified pseudo-filesystem mounted at /sys/fs/cgroup:

  • cpu.max: Enforces CPU bandwidth limits (e.g., allocating a quota of 50,000 microseconds per 100,000-microsecond period throttles the container to 0.5 CPU cores).
  • memory.max: Establishes hard memory ceilings. If processes inside that cgroup exceed this threshold, the kernel terminates processes within that group only, leaving host services untouched.
  • io.max: Caps disk read and write throughput and IOPS.

When you specify runtime constraints in Docker:

docker run -d \
  --name bounded-worker \
  --cpus="1.5" \
  --memory="512m" \
  my-app:latest
Enter fullscreen mode Exit fullscreen mode

The container runtime writes those exact numerical thresholds into /sys/fs/cgroup/docker/<container-id>/memory.max and cpu.max. The Linux kernel scheduler handles the actual enforcement at the hardware level.


3. Layered Storage: OverlayFS and Copy-on-Write

If every container required an independent duplicate of the operating system root filesystem, provisioning would be slow and drive storage would rapidly fill up. Docker achieves near-instant startup times and minimal disk overhead through OverlayFS, a union mount filesystem that implements Copy-on-Write (CoW).

An OverlayFS mount layers multiple directories and presents them to the container as a unified directory tree:

  • lowerdir: A stack of read-only image layers. Multiple running containers share the exact same read-only base layers on disk.
  • upperdir: A thin, isolated read-write directory created specifically for the running container instance.
  • workdir: An internal staging directory used by the kernel for atomic operations.
  • merged: The unified mount point that the container process actually interacts with.
       [ Merged View: / ]
      /                  \
+------------------------------+
|   Upper Layer (Read/Write)   |  <- Container runtime changes & new files
+------------------------------+
|   Lower Layer 2 (Read-Only)  |  <- Application binaries & runtime
+------------------------------+
|   Lower Layer 1 (Read-Only)  |  <- Base OS rootfs (Alpine, Debian, etc.)
+------------------------------+
Enter fullscreen mode Exit fullscreen mode

When a container modifies a file that originated in a lower layer, OverlayFS copies the file up into upperdir before applying the modification. The underlying base layer remains pristine, allowing dozens of containers to boot from a shared image without cross-contamination.

Simulating an Overlay Mount in Bash

# Create directory structure
mkdir -p lower upper work merged

# Populate base read-only layer
echo "base file v1" > lower/app.conf

# Mount using OverlayFS
sudo mount -t overlay overlay \
  -o lowerdir=lower,upperdir=upper,workdir=work \
  merged

# Verify the unified view
cat merged/app.conf  # Outputs: base file v1

# Modify the file within the merged mount
echo "modified for container" > merged/app.conf

# The base file remains untouched; modifications live exclusively in upper
cat lower/app.conf   # Outputs: base file v1
cat upper/app.conf   # Outputs: modified for container
Enter fullscreen mode Exit fullscreen mode

The Container Mental Model

Docker is not a hardware emulator. It is an orchestration wrapper that configures three mature Linux kernel subsystems:

  1. Namespaces establish isolation boundaries.
  2. Cgroups meter resource utilization.
  3. OverlayFS delivers fast, copy-on-write storage layers.

When troubleshooting container behavior—whether debugging DNS resolution, handling OOM terminations, or inspecting disk consumption—the solution is found in how the Linux kernel isolates and schedules ordinary processes.

Top comments (0)