Layer 3 — containerd (Container Runtime)

Doosan published on
5 min, 837 words

Categories: post

Part 3 of the Docker layers series. From top to bottom, the stack is:

  1. docker CLI → 2. dockerd → 3. containerd → 4. runc →
  2. Linux kernel. See the overview article.

Identity

containerd is a container runtime: the daemon that sits between dockerd above and runc below. It handles the hard parts of the container lifecycle: pulling and storing image content, unpacking it into a filesystem, and supervising containers from start to stop.

It is a CNCF graduated project and an industry standard. The same containerd powers Docker, most Kubernetes clusters, and tools such as nerdctl. That is exactly why it was split out of Docker.

Why it exists: breaking up the monolith

Docker was originally monolithic. As Docker modularized its architecture, the runtime became an independent project (Docker 1.11, 2016). Later, Kubernetes' CRI, or Container Runtime Interface, reinforced the split so other systems could use the runtime without the whole Docker engine:

        dockerd   ── needs ──►  containerd  ◄── needs ──  Kubernetes (kubelet)
       (Docker)                  (shared)                 (through CRI)

So containerd serves two very different owners through two APIs:

  • dockerd talks through containerd's native gRPC API.
  • Kubernetes talks through CRI, the gRPC API defined by Kubernetes.

Because of this double role, dropping dockerd in Kubernetes ("dockershim removal") did not change the actual runtime. Kubernetes simply talks directly to the same containerd.

What it owns

containerd owns runtime concerns: a layer more concrete than dockerd, but more abstract than runc.

  • Image management: pulls images, verifies digests, stores content in a content-addressable store, and manages snapshots/layers.
  • Snapshots: prepares container root filesystems from image layers through a snapshotter, such as overlayfs.
  • Container lifecycle: create, start, pause, stop, delete, and supervise running processes so they survive even if dockerd restarts.
  • Task: the actual running instance of a container.
  • Shim: a small containerd-shim-runc-v2 process attached to each container. It separates container I/O and exit status from the main containerd daemon.

It does not directly create namespaces or cgroups. That final step is delegated to runc.

shim: why containerd can restart safely

The key design point: containerd does not remain the direct parent of every container. Instead, it starts a shim for each container:

containerd
   └─ containerd-shim-runc-v2   ← lives for the container's lifetime
         └─ runc  (runs briefly to create the container, then exits)
               └─ my container process (PID 1 inside the container)

Because the long-lived parent is the shim, not containerd:

  • Running containers do not die when containerd or dockerd restarts or upgrades. The shim keeps them alive and reconnects.
  • The shim captures the container's stdout/stderr and reports the exit code back to containerd.

This is the structural reason runc rarely appears in ps: after it finishes its job and exits, the shim takes over.

Seeing it on this Mac

containerd runs inside the Colima VM:

$ colima ssh -- sh -c 'command -v containerd; ctr --version'
/usr/bin/containerd
ctr github.com/containerd/containerd v1.7.x

# `ctr` is containerd's low-level debug CLI, separate from docker:
$ colima ssh -- sudo ctr namespaces ls
NAME    LABELS
default
moby            # <- namespace used by dockerd inside containerd

moby is the containerd namespace used by Docker. Moby is the name of Docker's upstream open-source project. Seeing this namespace shows that dockerd is controlling containerd underneath.

containerd and surrounding layers

ConcernOwner
docker build, networking, volumes, Docker APIdockerd (Layer 2)
Image pull/storage, snapshots, lifecycle, shimcontainerd (Layer 3)
Namespace/cgroup creation, process execrunc (Layer 4)

Alternatives that fill this slot

  • CRI-O: a runtime built only for Kubernetes CRI. It is lighter than containerd for that single purpose and has no Docker API.
  • containerd with another low-level runtime: through runtime classes, containerd can choose runc, crun, gVisor (runsc), or Kata. See Layer 4.

Summary

  • containerd is the standard runtime for image content, snapshots, and container lifecycle.
  • It was split out of Docker so Kubernetes and other systems could use it directly through CRI and bypass dockerd.
  • The shim design lets containers keep running even when daemons restart.
  • The final low-level container creation is still delegated to runc, the next layer.

References