Docker path · What a container is · Topic 1 of 12

Containers vs VMs: namespaces, cgroups, one kernel

Not started

A container is not a lightweight virtual machine. It is a normal process on a Linux host that the kernel has fenced in: namespaces control what it can see, cgroups control how much it can use, and a separate root filesystem (the image) gives it its own files. There is no second kernel and no boot; starting a container costs about as much as starting the process inside it.

Why it matters: Almost every surprise with containers (slow stops, odd PIDs, memory kills, security limits) makes sense once you see that a container is an ordinary Linux process, not a small computer.

  1. 1Namespaces limit what it sees

    The process gets its own view of process IDs, network interfaces, mounts, hostname and IPC, so it looks alone on the machine.

  2. 2cgroups limit what it uses

    A control group caps memory, CPU time and other resources, and counts what the process and its children consume.

  3. 3One kernel for everyone

    Every container on a host shares the host's kernel. A VM boots its own kernel on virtual hardware; a container never does.

  4. 4Docker is the tooling around it

    The engine builds images, sets up networks and storage, and asks containerd and runc to start the fenced-in process.

1

How it works

The explanation, with examples you can run.

What a namespace hides

Each namespace type wraps one kind of global resource. Docker gives every container its own mount, PID, network, UTS (hostname) and IPC namespaces, and on cgroup v2 hosts its own cgroup namespace. User namespaces, which map root in the container to an unprivileged user outside, are only used if you enable rootless mode or userns-remap.

docker run --rm alpine:3.22 ps# PID   USER     TIME  COMMAND#     1 root      0:00 ps docker run --rm alpine:3.22 hostname# e758c8d8a3db          (the container ID, not your machine's name)

The process really is PID 1 inside its namespace, while the host sees it under an ordinary, larger PID. The same trick gives the container its own eth0, its own routing table and its own view of mounted filesystems.

What a cgroup limits

Namespaces do nothing about resources. A process in its own namespaces can still eat all the memory on the host. Control groups fix that: the kernel tracks every page and CPU slice used by a group of processes and enforces limits.

docker run --rm --memory 64m --cpus 0.5 alpine:3.22 \  sh -c 'cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/cpu.max'# 67108864# 50000 100000

64m became a hard memory ceiling of 67,108,864 bytes; --cpus 0.5 became a quota of 50 ms of CPU time per 100 ms period. Go over the memory limit and the kernel's OOM killer ends a process; go over the CPU quota and the process is simply paused until the next period (throttled). Kubernetes resource limits use exactly the same cgroup files.

Versus a virtual machine

A VM emulates hardware, boots its own kernel and runs a full OS on top. That gives strong isolation and lets you run Windows next to Linux, at the cost of seconds to boot and hundreds of megabytes of overhead. A container shares the host kernel, starts in milliseconds and adds almost nothing, but:

  • it can only run software for the host's kernel (Linux containers need a Linux kernel, which is why Docker Desktop runs a VM on macOS);
  • the kernel is the security boundary, so a kernel bug or an over-privileged container affects everyone on the host.

Many platforms combine the two: containers for packaging and density, inside VMs (or micro-VMs such as Firecracker) for isolation between tenants.

Where Docker fits

docker run asks the Docker daemon (dockerd) to create a container. The daemon prepares the image, network and mounts, then hands off to containerd, which uses runc to create the namespaces and cgroups and start your process. runc implements the OCI runtime specification, an open standard, which is why images built with Docker run unchanged under containerd, CRI-O or Podman.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.You run this. What does ps report as PID 1?
    docker run --rm alpine:3.22 ps
  2. 2.What makes /sys/fs/cgroup/memory.max read 67108864 inside a container started with --memory 64m?
  3. 3.Why is a kernel vulnerability more serious for containers than for VMs on the same host?

0 of 3 right so far. Get all three to finish this topic.