Adetayo Akinsanya unkletayo.dev

What Docker Adds on Top of Linux Kernel Isolation Primitives

Understanding why bare Linux namespaces and cgroups were unusable for developers until Docker unified them.

Adetayo Akinsanya (unkletayo) 2026-08-18

Part 1 in Series — Catch up on the previous article: Mastering Docker & Container Internals from First Principles: Series Introduction & Learning Roadmap (Part 0) before diving into this post.

In 2008, the Linux kernel possessed almost all the fundamental isolation features required to run containers:

  • Namespaces (clone(2) flags like CLONE_NEWPID, CLONE_NEWNET, CLONE_NEWNS) isolated process views of system resources.
  • Control Groups (cgroups) capped CPU, memory, and disk I/O usage.
  • Chroot & Union Filesystems provided isolated root filesystems.

Yet, despite having these powerful kernel features, almost no software team ran microservices in containers.

Setting up a containerized environment required writing 200 lines of complex C system calls, mounting virtual /proc filesystems manually, setting up veth virtual ethernet pairs using raw ip link commands, and configuring cgroup filesystem control knobs in /sys/fs/cgroup/.

If a single step in the shell script failed, the process leaked network interfaces or left un-isolated host directories exposed.

Then, in 2013, Docker appeared.

Docker did not invent container isolation. The Linux kernel already provided the primitives.

What Docker did was solve the Developer Experience and Distribution Problem.


The Raw Primitive Gap

To understand what Docker built, we must contrast a raw Linux kernel primitive with the developer abstraction Docker introduced.

RAW LINUX KERNEL PRIMITIVES                   DOCKER ENGINE ABSTRACTION
+---------------------------------------+     +---------------------------------------+
|  sys_clone(CLONE_NEWPID | NEWNET...)  | --> |  docker run -d --name web -p 80:80 \  |
|  /sys/fs/cgroup/memory/my_app/        |     |             -m 512m nginx             |
|  mount -t overlay ...                 |     |  (Single unified CLI command!)        |
+---------------------------------------+     +---------------------------------------+

Without Docker, running an isolated web server required manual orchestrations across five separate kernel subsystems:

  1. Process Isolation: Invoking unshare or clone(2) with 6 distinct namespace flags.
  2. Filesystem Pivot: Extracting a rootfs tarball, creating tmpfs mount points, and invoking pivot_root(2).
  3. Resource Limits: Creating subdirectories in /sys/fs/cgroup/, calculating byte thresholds, and echoing the target PID into /sys/fs/cgroup/.../cgroup.procs.
  4. Network Virtualization: Creating a veth pair, moving one end into the container network namespace, setting up a bridge interface docker0, assigning IP addresses, and adding iptables NAT rules for port forwarding.
  5. Image Distribution: Packaging app dependencies into standardized, content-addressable, layered image archives.

The Four Pillar Abstractions Added by Docker

Docker unified these fragmented kernel interfaces into four main developer abstractions:

+-------------------------------------------------------------------+
|                     THE DOCKER ENGINE LAYER                       |
+-------------------------------------------------------------------+
|  1. Unified Container Lifecycle Engine (`docker run/stop/rm`)     |
|  2. Standardized Image Specification & Layered Builds (Dockerfile)|
|  3. Centralized Registry Distribution Protocol (Docker Hub)       |
|  4. Automated Virtual Networking & DNS Service Discovery          |
+-------------------------------------------------------------------+
                                  |
                                  v
+-------------------------------------------------------------------+
|                   LINUX KERNEL SUBSYSTEMS                         |
|   Namespaces  |  cgroups  |  OverlayFS  |  veth & iptables        |
+-------------------------------------------------------------------+

1. The Standardized Container Image Format

Before Docker, applications were packaged as RPMs, DEBs, or tarballs. If a target server lacked a specific library version (e.g., libssl.so.1.1), the deployment failed.

Docker created the OCI Image Format: a self-contained archive containing:

  • The complete root directory filesystem (rootfs).
  • Every system library and binary required by the application.
  • A JSON configuration file defining environment variables, entrypoint commands, and default working directories.

An image built on a developer’s laptop runs identically on an AWS EC2 instance because the container carries its entire user-space environment with it.


2. The Layered Build System (Dockerfile)

Docker introduced Dockerfile syntax (FROM, RUN, COPY, CMD) and a Content-Addressable Layered Build System.

Instead of copying 2 Gigabytes of rootfs files for every update, Docker breaks images into immutable read-only layers. If two applications use ubuntu:22.04 as a base, both containers share the exact same base layer on disk in RAM via OverlayFS.

+---------------------------------------------------------------+
| Container Writable Layer (Read/Write Delta)                    |
+---------------------------------------------------------------+
| Layer 3: Application Jar (15 MB)                              |
+---------------------------------------------------------------+
| Layer 2: OpenJDK 17 Runtime (250 MB)                          |
+---------------------------------------------------------------+
| Layer 1: Ubuntu 22.04 Base OS (75 MB) [SHARED BY ALL]         |
+---------------------------------------------------------------+

3. Automated Virtual Networking and DNS

Before Docker, establishing network connectivity for a container required manual Linux networking configuration:

# Manual Linux Networking setup (WITHOUT Docker):
ip link add veth0 type veth peer name veth1
ip link set veth1 netns <container_pid>
ip addr add 192.168.1.2/24 dev veth1
ip link set veth0 master br0
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.2:80

Docker replaced all of this manual networking with a single flag: -p 8080:80.

The Docker daemon automatically constructs virtual bridges (docker0), creates veth pairs, manages private IP allocations, updates host iptables forwarding rules, and runs an embedded DNS server (127.0.0.11) inside container network namespaces so containers can communicate using service names (http://payment-service:8080).


4. Centralized Distribution Protocol (Registries)

Docker specified the Docker Registry HTTP API V2.

With docker push and docker pull, developers can publish built images to any registry (Docker Hub, Amazon ECR, Google Artifact Registry).

Storage is optimized using SHA-256 Content-Addressable Digests. If a registry already holds a specific 200MB layer digest, pushing a new image version that reuses that layer transfers only the modified 5MB delta layer across the network.


Summary & Next Steps

Docker transformed containerization from a complex Linux kernel administration task into a streamlined developer workflow:

  • The Linux kernel provides raw primitives: Namespaces (visibility), cgroups (resource limits), and OverlayFS (filesystems).
  • Docker adds standard abstractions: Layered Images, Dockerfiles, Automated Networking/DNS, and Registry Protocols.

In the next article, we dive deep into The Docker Toolchain: CLI to Daemon to containerd to runc.

References & Further Reading

  1. Linux Man-Pages Project. namespaces(7) — Overview of Linux Namespaces. Linux Kernel Docs.
  2. Linux Kernel Organization. cgroups(7) — Control Groups. Linux Kernel Docs.
  3. Kerrisk, M. (2010). The Linux Programming Interface (Chapters 19 & 38). No Starch Press.

Up Next in Series →

Part 2: The Docker Toolchain Deep Dive: CLI to Daemon to containerd to runc

Continue to Part 2 →