Engine architecture: dockerd, containerd, runc

Docker · Expert · 12 min

Architecture knowledge explains daemon restarts, alternative runtimes and Kubernetes' relationship to Docker.

docker run passes through several components, each with a narrow job.

  • docker CLI: a client that sends REST calls to the daemon's API, by default over /var/run/docker.sock (or a remote host through DOCKER_HOST or contexts).
  • dockerd: the Docker Engine. It owns Docker's user-facing features: the API, image builds (via BuildKit), networks, volumes, Compose support and logging drivers.
  • containerd: a container supervisor (a graduated CNCF project) that manages images, snapshots (filesystem layers) and container lifecycles behind a gRPC API. Kubernetes talks to containerd directly through CRI, which is why Kubernetes removed the Docker-specific shim and why "Kubernetes dropped Docker" changed little for images.
  • containerd-shim-runc-v2: one small shim per container. It becomes the container's parent, holds its stdio and exit status, and lets dockerd and containerd restart without killing containers (enable live-restore in daemon.json for the full effect).
  • runc: the OCI runtime. It reads the bundle's config.json, creates namespaces and cgroups, applies capabilities, seccomp and mounts, execs the container process, then exits. It is not a long-running daemon.

Because the runtime is pluggable, you can swap it per container: --runtime runsc (gVisor, a user-space kernel for strong isolation), Kata Containers (lightweight VMs) or alternative OCI runtimes such as crun or youki. docker info shows registered runtimes and the default.

Tools exist at each level: ctr and nerdctl talk to containerd directly, and runc list shows runc's view. Docker namespaces its containers in containerd as moby, so sudo ctr -n moby containers ls lists them.

Example

docker run -d --name demo nginx:1.27
ps -o pid,ppid,cmd -C containerd-shim-runc-v2,nginx
#   PID  PPID CMD
#  4120     1 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 5c1e...
#  4141  4120 nginx: master process nginx -g daemon off;

sudo ctr -n moby containers ls          # same container, seen by containerd
docker info --format '{{.DefaultRuntime}} {{json .Runtimes}}'

# /etc/docker/daemon.json: keep containers running across daemon restarts
#   { "live-restore": true }
sudo systemctl restart docker && docker ps   # demo is still up

docker run --rm --runtime runsc alpine uname -a   # if gVisor is installed

Remember

  • CLI -> dockerd (API, builds, networks) -> containerd -> shim -> runc
  • runc sets up the container and exits; the shim stays as its parent
  • Shims plus live-restore keep containers alive across daemon restarts
  • Runtimes are pluggable: gVisor, Kata, crun via --runtime

Watch out for

  • Exposing the Docker API socket or TCP port without TLS — it grants root on the host.

Check yourself

  1. Which component remains the parent process of a running container after it starts?
  2. Kubernetes removed dockershim. Why do images built with Docker still run on it?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.docker.com/engine/daemon/alternative-runtimes/