Docker from First PrinciplesPost 2 of 18 · 18 of 18 written
- How to use this guide
- 01The problem Docker solves
- 02What a container really is
- 03Images: layers, hashes and registries
- 04Docker's architecture
- 05Container lifecycle
- 06Dockerfiles in depth
- 07Storage: where data lives
- 08Networking
- 09Docker Compose for multi-service AI apps
- 10GPUs in containers
- 11Building good images for AI workloads
- 12Serving and production patterns
- 13Security
- 14Debugging and introspection
- 15Beyond Docker
- 16Hands-on learning path
- 17Question bank
- 18Glossary and cheat sheet
2. What a container really is
There is no “container” object in the Linux kernel. A container is an ordinary process that the kernel shows a restricted view of the world (namespaces), caps in resources (cgroups), roots in a different directory tree (the image’s filesystem), and strips of some privileges (capabilities, seccomp). Understand those four mechanisms and Docker stops being magic.
Start from processes
Every running program is a process: it has a PID, a user ID, open files, memory, and a view of the filesystem and network. Start docker run -d --name s alpine sleep 1000 on a Linux machine, then run ps aux | grep sleep on the host: the sleep process is right there, like any other. docker inspect -f '{{.State.Pid}}' s prints its host PID. On your Mac this happens inside Docker Desktop’s VM, which is why you don’t see it in Activity Monitor.
Namespaces: what a process can see
A namespace gives a process its own private copy of one kind of global resource. Docker puts each container in a fresh set of them.
| Namespace | What it isolates | What you notice in practice |
|---|---|---|
| PID | Process IDs | The container’s main process sees itself as PID 1 and cannot see host processes |
| Mount | Mount table | The container has its own /, built from the image, plus its own mounts |
| Network | Interfaces, IPs, routes, ports, localhost |
localhost inside a container means the container itself, not your machine |
| UTS | Hostname | The container’s hostname is its short ID unless you set one |
| IPC | Shared memory, message queues | Matters for /dev/shm, which PyTorch DataLoader workers use (Part 10) |
| User | UID and GID mapping | Can make root-in-container an unprivileged user outside; off by default in Docker |
| Cgroup | The view of the cgroup tree | The container sees its own limits as the root of the tree |
You can see a process’s namespaces with ls -l /proc/<pid>/ns or lsns. The most common beginner bug comes straight from the network namespace: a server bound to 127.0.0.1 inside a container is unreachable from outside it, because that loopback belongs to the container alone.
Control groups: what a process can use
Cgroups limit and account for CPU, memory, number of processes and disk I/O. --memory 8g writes a memory limit into the container’s cgroup; if the process exceeds it, the kernel’s OOM killer kills it, and Docker reports exit code 137 with OOMKilled: true. --cpus 4 sets a CPU-time quota. Without flags, a container can use the whole machine.
One ML-relevant trap: limits are enforced, but not always visible. Python’s os.cpu_count() inside a container limited with --cpus 4 still reports every host core, so PyTorch, NumPy or a DataLoader may start far more threads than the quota allows and run slower. Set OMP_NUM_THREADS, MKL_NUM_THREADS or torch.set_num_threads() explicitly. Modern Linux distributions use cgroup v2, a single unified hierarchy; older guides describe v1.
The root filesystem
When the container starts, its process gets the image’s filesystem as / (Docker uses pivot_root, a stronger cousin of chroot). That’s why an Ubuntu-based container has apt and an /etc/os-release saying Ubuntu, even on a Fedora host. But run uname -r inside it and you get the host’s kernel version: the user space came from the image, the kernel did not.
Privilege restrictions on top
- Capabilities split root’s powers into about 40 separate rights. Docker gives containers only a small default set; for example, changing network configuration (
CAP_NET_ADMIN) or broad system administration (CAP_SYS_ADMIN) is withheld. - seccomp filters which system calls a process may make. Docker’s default profile blocks dozens of rarely needed, risky calls.
- AppArmor or SELinux add mandatory access rules on hosts that use them.
--privilegedturns most of this off and gives the container nearly full access to the host’s devices. Treat it as a red flag in any instructions you copy.
Containers versus virtual machines
| Aspect | Container | Virtual machine |
|---|---|---|
| Kernel | Shares the host kernel | Runs its own guest kernel |
| Start time | Milliseconds to seconds: just a process start | Seconds to minutes: an OS boot |
| Size | Megabytes to a few gigabytes | Gigabytes of disk image |
| Isolation strength | Process-level; a kernel bug can break it | Hardware-level; much harder to escape |
| GPU access | Device files passed in, near-native speed | PCI passthrough or vGPU, more setup |
| Other operating systems | Linux containers need a Linux kernel | Any OS the hypervisor supports |
This table explains Docker Desktop: macOS has no Linux kernel, so Docker Desktop runs a small Linux VM and your containers live inside it. Part 13 covers microVMs such as Firecracker and gVisor, which blur the line when you need VM-like isolation with container-like speed.