CoursesDocker in depthThe Docker engine

Engine architecture

dockerd, containerd, shim, and runc.

Intermediate12 min · lesson 5 of 30

“Docker” is not one program but a small stack of daemons, and knowing the split explains a surprising amount of behaviour. The Docker daemon (dockerd) serves the API and manages images, networks, and volumes. It delegates container lifecycle to containerd. containerd starts a lightweight shim process per container, and the shim invokes runc — the low-level OCI runtime that makes the actual Linux calls (clone, namespaces, cgroups, pivot_root) to become the container. runc then exits; the shim stays as the container’s parent.

From your command to a running container
1docker CLI
REST call over the socket
2dockerd
images, networks, volumes
3containerd
container lifecycle + CRI
4shim → runc
creates the process, then runc exits
runc sets up namespaces + cgroups and execs your process; the shim keeps it attached to containerd after runc is gone.
terminal
$ ps -ef | grep -E "dockerd|containerd|shim" | grep -v grep
root dockerd
root containerd
root containerd-shim-runc-v2 -namespace moby -id 7f8e9d0a... -address /run/containerd/...
# └ one shim per running container; note there is no runc — it already exited

Scenario: restart the daemon without an outage

You need to change daemon.json, which requires restarting dockerd — but you cannot drop the running containers. Because the shim (not dockerd) is each container’s parent, containers survive a daemon restart; with live-restore enabled they keep running even while dockerd is down, and reconnect when it returns. Turn it on so daemon maintenance is not a workload outage.

terminal
# /etc/docker/daemon.json -> { "live-restore": true }
$ sudo systemctl restart docker
$ docker ps # the containers are still Up — the shims held them
CONTAINER ID IMAGE STATUS
7f8e9d0a1b2c nginx:1.27 Up 40 minutes

Scenario: no dockerd at all (why Kubernetes dropped it)

On a Kubernetes node there is often no Docker daemon — the kubelet talks to containerd directly through the CRI, because dockerd was just an extra hop. You can inspect that lower layer with containerd’s own client, ctr, or the friendlier nerdctl. Seeing the same containers through ctr that you started through docker makes the layering concrete: docker is a convenience on top of containerd, not the thing running your containers.

terminal
$ sudo ctr -n moby containers ls # containerd’s own view (moby = docker’s namespace)
CONTAINER IMAGE RUNTIME
7f8e9d0a... docker.io/nginx:1.27 io.containerd.runc.v2
# on a k8s node the namespace is k8s.io, and there is no dockerd in ps at all
The daemon runs as root; the socket is a root credential
dockerd runs as root and listens on /var/run/docker.sock. Anyone who can talk to that socket can start a container that bind-mounts the host root filesystem and read or write anything as root — so socket access is root-equivalent. Never expose it casually (least of all over TCP without TLS); guarding it, and running rootless, are core topics of the advanced course.