Unmasking Docker: From Linux Syscalls to Hardware Virtualization

Published 2026-07-17

Introduction: The Myth of the “Lightweight VM”

If asked “What is Docker?” in an interview, 90% of developers will immediately answer: “It’s a lightweight virtual machine.”

This might be the biggest lie of the cloud computing era.

The truth is: In the Linux kernel source code, the concept of a “container” simply does not exist. There is no system call named create_container. What we call a container is merely an ordinary Linux process operating under a set of sophisticated illusions. Today, we will shatter the surface-level understanding of Docker, drilling down into the Linux kernel, Page Caches, and breaking through the OS layer directly to the core of CPU hardware virtualization.

Part 1: Architecture of Inception — The Three Pillars of Containers

If a container is just a process, how does it trick us into thinking it’s an independent machine? It relies on three fundamental kernel mechanisms.

1. Namespaces (Parallel Universes)

Normally, we create child processes using fork(), but container engines use the lower-level clone() system call. By passing specific flags (like CLONE_NEWPID, CLONE_NEWNS), the resulting process is essentially fitted with a “VR headset”. In its isolated reality, it believes it is PID 1, possessing its own network interface and hostname, completely unaware of other processes on the host.

2. Cgroups (The Restrictive Halo)

Isolation isn’t enough; a process could still consume infinite memory. Many assume resource limitation requires complex APIs, but the Linux philosophy is “everything is a file.” Container engines don’t need a specific system call to limit resources. They simply create a directory in the host’s Virtual File System (VFS) and write the PID and memory limit into a file. The kernel enforces these limits automatically:

mkdir /sys/fs/cgroup/memory/docker/my-container
echo 512000000 > /sys/fs/cgroup/memory/docker/my-container/memory.limit_in_bytes
echo $$ > /sys/fs/cgroup/memory/docker/my-container/cgroup.procs

3. UnionFS (The Layered Cake)

How does a container get its own filesystem? It utilizes Union Filesystems like Overlay2. It stacks multiple read-only image layers and adds a writable layer on top. Finally, the container engine executes a ruthless system call: pivot_root(). This not only changes the root directory of the process to the new mount point but entirely unmounts the host’s original root directory. The bridge is burned, and the host OS disappears from the container’s worldview forever.

Part 2: Tearing the Rift — The Hardcore Truth of docker exec

When you run docker exec -it my-container bash, do you think that bash process is spawned by the container’s main process (e.g., Python)?

Absolutely not. They share no bloodline; they are merely “roommates.”

The setns() Time Travel

The underlying tool, runc, uses the standard open() syscall to access the magic file handlers exposed by the target Python process under /proc/<PID>/ns/. Then comes the core protagonist: setns(fd, 0). This syscall forces the current process to “hijack” its way into the target process’s parallel universe. Once inside, runc calls fork() to spawn a child, and then uses execve() to morph into bash. This is why inside the container, Python is PID 1 and bash is PID 2, yet both report a Parent PID (PPID) of 0.

The Limits of God Mode: User Namespaces

Why can’t a container’s root modify core host files? Linux divides privileges into granular Capabilities. The kernel has heavily modified its underlying privilege check logic via ns_capable(). When a container root tries to modify a host file, the kernel doesn’t just check if it has “God privileges”; it verifies the scope of those privileges. Upon detecting an out-of-bounds access, it instantly revokes the privileges, treating the process as a standard host user.

Part 3: Physical Multiplexing — Squeezing Memory to the Limit

If you boot 10 Ubuntu containers, are there 10 copies of the bash binary in your RAM? No. This is Docker’s ultimate resource-saving secret.

Page Cache and Inodes

When a file is read into memory, the Linux kernel caches it in the Page Cache via the address_space structure (backed by an efficient XArray tree). The unique identifier for this mapping is the file’s Inode (physical pointer). Because of OverlayFS, if 10 containers haven’t modified the underlying Ubuntu files, they are all reading from the exact same read-only layer — meaning the same Inode. The kernel is smart: when encountering the same Inode, it uses the mmap mechanism to map the virtual memory of all 10 processes to the exact same physical memory block. Docker achieves massive memory sharing without writing a single line of code for it — it just leverages the OS’s native magic.

Part 4: Crossing the Boundary — Cross-Architecture and True Virtualization

How does an ARM container run on an x86 host?

The kernel has a miraculous feature called binfmt_misc. When it encounters an unexecutable ARM ELF file, instead of crashing, it intercepts it and hands it over to a pre-registered translator (like QEMU or Apple’s Rosetta 2 for Linux). The translator converts ARM instructions to x86 instructions in real-time for the physical CPU.

But here lies a critical pain point: macOS and Windows have entirely different kernels. They have no Namespaces or Cgroups. What then? The only answer is to boot a real Linux Virtual Machine (VM). This brings us to the ultimate technological boundary.

Part 5: The End of Virtualization is Fusion — Inside WSL 2

Traditional software-simulated VMs have terrible performance. Modern Hypervisors (like Hyper-V or KVM) utilize Hardware-Assisted Virtualization.

Two-Stage MMU and VM Exit

Physical CPUs feature VMX Root (Host) and Non-root (Guest) modes. The VM’s OS runs directly on physical circuitry. If it attempts an unauthorized action, the hardware instantly triggers a VM Exit (a Trap), handing control back to the Hypervisor. Memory isolation relies on hardware-level two-stage page tables (like Intel’s EPT or ARM’s VTTBR_EL2 register). The VM translates addresses once, and the hardware MMU transparently performs a second translation, ensuring absolute physical isolation.

WSL 2: A Utility VM in Disguise

Docker on Windows utilizes WSL 2 under the hood (a highly customized Utility VM). Why is it so fast?

  1. Lightning Boot: No BIOS or GRUB. Hyper-V bootstraps the kernel directly.
  2. Dynamic Memory: Through Page Reporting, the Linux kernel proactively returns unused memory back to Windows in real-time.
  3. 9P Protocol Wormholes: When mounting host directories, it utilizes the 9P protocol over a high-speed memory bus (VMBus), breaking down OS file system barriers seamlessly.

Conclusion: The Art of Abstraction

At this point, the boundary between containers and virtual machines is crystal clear:

  • Containers (Docker): An abstraction and isolation layer over the Operating System APIs (Syscalls, VFS).
  • Virtual Machines (Hypervisor): An abstraction and isolation layer over the Underlying Hardware (Instruction Sets, MMU).

Technological evolution is the constant stacking of abstractions. However, only when we possess the ability to pierce through these layers and observe the mechanics of kernels and hardware do we truly gain the “God perspective” needed to solve complex performance bottlenecks and elusive bugs.