What happens when you type docker run nginx
It looks like one command. It is actually four programs cooperating.
┌─────────────┐ ┌──────────────┐ ┌────────────┐ ┌────────┐
│ Docker CLI │───▶│ dockerd │───▶│ containerd │───▶│ runc │
│ (docker) │ │ (REST daemon)│ │ (lifecycle)│ │ (OCI) │
└─────────────┘ └──────────────┘ └────────────┘ └────────┘Each layer's job
- Docker CLI (
docker) — the command-line client you actually type. Talks to dockerd over a Unix socket (/var/run/docker.sock) or TCP. - dockerd — the background daemon. Manages images, networks, volumes, and the public Docker REST API. This is what restarts when you "restart Docker."
- containerd — the runtime that actually manages container lifecycle: pulls images, starts processes, attaches to namespaces. CNCF graduate. Runs containers for both Docker AND Kubernetes.
- runc — the low-level OCI-compliant runtime. Spawns the actual Linux process inside namespaces and cgroups. Tiny. Boring. Critical.
Why the layers matter
You can swap pieces. Kubernetes' kubelet talks to containerd directly, skipping dockerd entirely. Podman skips dockerd too — it's a CLI that drives runc directly, daemonless. The OCI standard is what makes this swappability possible.