Free — no signup required

The Standard Layer Cake

3 min read

Tracing a Container from Command to Process

Let's follow a single docker run nginx command all the way down to a running process on the Linux kernel. This is the standard path in a modern Docker installation.

Step 1 — Docker CLI (docker):
You type the command. The Docker CLI parses your arguments, authenticates if needed, and sends an API request over a Unix socket (/var/run/docker.sock) to the Docker daemon (dockerd). The CLI itself does no container work; it is purely a user interface.

Step 2 — Docker Daemon (dockerd):
dockerd receives the API request. It handles higher-level Docker-specific features like Docker Compose integration, Docker Swarm orchestration, and the Docker API surface. For the actual container work, it immediately delegates to containerd.

Step 3 — containerd (High-Level Runtime):
containerd is a background daemon responsible for the container's entire lifecycle. It pulls the image from the registry if it is not cached locally, unpacks the image layers into a merged filesystem using a snapshotter (like overlayfs), sets up the network namespace, and prepares the OCI bundle (the unpacked filesystem plus a config.json). Once everything is ready, it calls the low-level runtime.

Step 4 — containerd-shim:
Before calling runc, containerd spawns a lightweight process called containerd-shim-runc-v2. This shim process acts as a stable parent for the container process. It allows containerd itself to be restarted without killing running containers, and it handles the container's stdin/stdout/stderr streams (which is how docker logs works).

Step 5 — runc (Low-Level Runtime):
runc is a small, standalone CLI tool — the OCI reference implementation. It reads the config.json prepared by containerd, makes the appropriate Linux kernel calls to create namespaces (PID, network, mount, UTS, IPC, user) and cgroups, sets up the root filesystem, drops privileges, and spawns the container's main process. Once the process is running, runc exits completely. The container process is now a child of the shim, not of runc.

This design means runc has a minimal attack surface. It is a short-lived tool, not a persistent daemon, which limits the window during which it could be exploited.

The Key Insight: containerd Without Docker

Because containerd is a standalone daemon with its own API (gRPC), Kubernetes can talk to it directly — bypassing Docker entirely. This is exactly what modern Kubernetes clusters do. The dockerd layer is completely absent. Kubernetes speaks to containerd via the Container Runtime Interface (CRI), a standard gRPC API that Kubernetes defines for any compliant runtime. containerd ships with a CRI plugin that implements this interface.

This is why the Kubernetes project deprecated the "dockershim" (a compatibility shim that made dockerd look like a CRI runtime) in Kubernetes 1.20 and removed it in 1.24. It was an unnecessary layer. containerd already spoke CRI natively.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free