You can manage container tasks directly with the containerd API, but the most surprising thing is how much of the "container runtime" heavy lifting is actually handled by containerd itself, not Docker or Kubernetes, when you use it this way.

Let’s see containerd in action, managing a simple container. First, we need a container image. We’ll use docker.io/library/alpine:latest.

ctr images pull docker.io/library/alpine:latest

Now, let’s create a container from that image. We’ll give it a name, my-alpine-container.

ctr containers create docker.io/library/alpine:latest my-alpine-container

This command doesn’t actually run the container; it just sets up the container object within containerd’s management. Next, we need to create a "task" for this container. A task is the actual running instance, the process group.

ctr tasks create my-alpine-container

This command spawns the container’s entrypoint process. If you were to run ctr tasks list, you’d see my-alpine-container listed with a PID. To start the container’s process, we use start.

ctr tasks start my-alpine-container

Now, if you ctr tasks list again, you’ll see the state as RUNNING. To get a shell inside, we can execute a new process within the existing task’s namespace.

ctr tasks exec --exec-id shell my-alpine-container sh

This exec-id is crucial. It’s how containerd tracks individual processes spawned within a running container task. You can run multiple exec commands, each with a unique ID, and they’ll all be children of the initial container task.

The problem containerd solves is providing a low-level, consistent API for managing the container lifecycle – from image fetching and unpacking to process creation and networking setup – independently of higher-level orchestrators like Kubernetes or user-facing tools like Docker. It abstracts away the specifics of different container runtimes (like runc, crun, or even Kata Containers) behind a gRPC interface.

Internally, when you ctr tasks create, containerd interacts with its configured CNI plugin to set up networking, then uses its chosen OCI runtime (usually runc by default) to create the container’s filesystem, namespaces, and finally, launch the initial process specified in the container’s OCI configuration. The ctr tasks start command signals the OCI runtime to begin execution of that process.

The containerd API exposes concepts like Image, Container, and Task. An Image is a reference to an unpacked image layer set on disk. A Container is a logical grouping of resources (filesystem, metadata) that an Image is unpacked into. A Task is the actual running instance of a Container, encompassing its processes and associated lifecycle. You can also manage Snapshots (the container’s filesystem layers) and Leases (short-lived references for garbage collection).

When you use ctr, you’re directly interacting with these containerd concepts. For instance, ctr images pull fetches image layers and stores them as a snapshot. ctr containers create creates the Container object and associates it with a specific image snapshot. ctr tasks create generates the OCI runtime configuration and prepares the task, and ctr tasks start executes it.

Many people assume that when Kubernetes uses containerd, it’s just a thin shim. In reality, Kubernetes’s Container Runtime Interface (CRI) implementation for containerd (cri-containerd) directly calls the containerd API. This means Kubernetes is asking containerd to pull images, create containers, and start tasks, and containerd is doing all the heavy lifting with runc or its equivalent. The containerd API is the single source of truth for the container’s runtime state.

The containerd API also manages the lifecycle of the container’s filesystem. When you create a container, containerd uses its snapshotter (like overlayfs or native) to prepare a writable layer on top of the image layers. This snapshot is what the container process actually sees as its root filesystem. When a container is deleted, containerd is responsible for cleaning up this snapshot, ensuring no stale filesystem data is left behind.

The next step in mastering containerd is understanding how to manage its network attachments and process execution more granularly.

Want structured learning?

Take the full Containerd course →