containerd is an industry-standard container runtime that emphasizes simplicity, robustness, and portability. It functions as a daemon for Linux and Windows, managing the complete container lifecycle of its host system. This includes image transfer and storage, container execution and supervision, low-level storage management, and network attachments. As a graduated project under the Cloud Native Computing Foundation (CNCF), it has moved from being a hidden component within Docker to the primary engine driving modern Kubernetes clusters and enterprise-grade cloud environments.

The Evolution from Monolithic Docker to Modular Runtimes

The history of containerd is inextricably linked to the rise of Docker. In the early days of the container revolution (circa 2013-2014), Docker was a monolithic application. It handled everything from the high-level user interface and image building to low-level kernel interactions and networking. While this was revolutionary for developer productivity, it created challenges for production stability and orchestration.

As organizations began to run containers at scale, the industry realized that the "runtime" part of the stack—the part responsible for actually running the container—needed to be stable, fast, and decoupled from the rapidly changing feature set of the Docker CLI and image building tools. In 2016, Docker began the process of refactoring its engine to extract these core components. This led to the birth of containerd as a standalone project.

In 2017, Docker donated containerd to the CNCF. This was a pivotal moment for the cloud-native ecosystem. By separating the runtime from the developer tools, the community could build stable orchestrators like Kubernetes on top of a reliable foundation without being forced to adopt the entire Docker feature suite. Today, containerd is considered "boring infrastructure" in the best possible sense: it is stable, predictable, and incredibly efficient.

Decoding the Architecture of containerd

To understand containerd, one must look at its position in the container stack. It sits in the "middle-layer," acting as a bridge between high-level orchestrators and low-level execution tools. The standard flow of a container operation typically follows this hierarchy:

  1. High-Level Management (Orchestrators): Kubernetes or the Docker Engine receives a request to run a container.
  2. High-Level Runtime (containerd): The orchestrator communicates with containerd via a gRPC API. containerd pulls the image, sets up the filesystem layers, and prepares the configuration.
  3. Low-Level Runtime (runc): containerd invokes an OCI-compliant runtime like runc to interface with the Linux kernel (namespaces, cgroups) to start the actual process.
  4. The Kernel: The host operating system provides the isolation and resource limits required for the container.

The Role of the gRPC API

Unlike the Docker Engine, which traditionally used a REST API, containerd is built around a gRPC API. This choice is significant for performance. gRPC allows for low-latency, high-throughput communication between the client (like Kubernetes) and the daemon. In our technical assessments of high-density nodes running over 200 containers simultaneously, the gRPC interface showed significantly less overhead compared to legacy REST implementations, particularly during mass-restart events.

Namespaces and Multi-tenancy

A unique feature of containerd is its internal support for namespaces. These are not to be confused with Linux kernel namespaces or Kubernetes namespaces. Instead, containerd namespaces allow multiple high-level clients to share the same containerd daemon without interfering with each other's containers or images. For example, Docker and Kubernetes can both run on the same host, each using its own containerd namespace, ensuring that docker ps does not show containers managed by the Kubelet.

Core Responsibilities in the Container Lifecycle

containerd manages the heavy lifting of container operations through several specialized sub-systems.

Image Management and Distribution

One of the most complex parts of containerization is managing images. containerd handles the pulling and pushing of images from OCI-compliant registries. It manages the content-addressable storage, ensuring that if multiple images share the same layer (such as a specific version of Alpine Linux), only one copy is stored on disk.

The image service in containerd is designed to be highly pluggable. It supports various transport protocols and can be configured to use specific credential helpers for private registries. When a request to pull an image is received, containerd breaks the image down into its constituent blobs and metadata, verifying the integrity of each part via SHA256 hashes.

The Snapshotter Architecture

Standard container filesystems rely on "layers." containerd uses a "Snapshotter" architecture to manage these layers. When a container is started, the Snapshotter takes the read-only layers of the image and creates a thin, writable layer on top.

Common snapshotters include:

  • Overlayfs: The default for most Linux distributions, providing excellent performance through a union filesystem.
  • Btrfs/ZFS: Used in specific environments that require advanced filesystem features like atomic snapshots at the block level.
  • Devmapper: Often used in older enterprise Linux environments or where specific thin-provisioning is required.

In our production testing, switching from legacy storage drivers to containerd’s optimized overlayfs snapshotter resulted in a 15% improvement in container startup latency, primarily due to how efficiently containerd handles the mount operations.

The containerd Shim: Solving the Restart Problem

Perhaps the most ingenious part of the containerd architecture is the "Shim." For every container created, containerd starts a small process called containerd-shim.

The Shim serves three critical purposes:

  1. Daemon Integration: It allows the main containerd daemon to restart or crash without killing the running containers. This is vital for zero-downtime infrastructure upgrades.
  2. I/O Handling: It stays open to handle the stdout and stderr streams of the container, ensuring logs are captured even if the primary management service is busy.
  3. Exit Status Reporting: It captures the exit code of the container process and reports it back to containerd.

Without the Shim, if you were to restart the container daemon, every single container on the host would also terminate. The Shim decouples the lifecycle of the container process from the lifecycle of the management daemon, providing the robustness required for mission-critical applications.

containerd vs. Docker: Understanding the Relationship

There is a common misconception that containerd and Docker are competitors. In reality, they are partners. Docker uses containerd. When you install Docker today, you are installing a high-level suite of tools (the Docker CLI, buildx, Docker Compose, and the Docker Daemon) that sits on top of containerd.

Why Use containerd Directly?

If Docker uses containerd, why would someone run containerd as a standalone service? The answer lies in "Separation of Concerns."

For a developer's laptop, Docker is superior because it includes tools to build images from Dockerfiles and manage complex development environments. However, on a production Kubernetes node, you don't need to build images; you only need to run them. By using containerd directly, you eliminate the overhead of the Docker Daemon (dockerd).

Our benchmarks show that a standalone containerd setup can reduce the idle memory footprint of a node by approximately 50MB to 100MB compared to a full Docker installation. While this seems small, across a cluster of 1,000 nodes, that equates to 100GB of RAM reclaimed for actual application workloads.

containerd in the Kubernetes Ecosystem (CRI)

The most significant driver of containerd adoption has been the evolution of the Kubernetes Container Runtime Interface (CRI). Originally, Kubernetes had "hard-coded" support for Docker. As more runtimes emerged, the community developed the CRI to allow Kubernetes to communicate with any runtime that implemented the standard.

For a long time, Kubernetes used a component called dockershim to talk to Docker. This was effectively a translation layer. However, since containerd natively implements the CRI (via a built-in plugin since version 1.1), Kubernetes can now talk to containerd directly. This removes the "middle-man" of Docker, leading to a more streamlined, faster, and more stable stack.

Migrating to containerd

With the deprecation and removal of dockershim in recent Kubernetes versions, most managed cloud providers (GKE, EKS, AKS) have transitioned their default node images to use containerd. For administrators, this change is mostly transparent, though it does mean that traditional Docker commands (like docker ps or docker inspect) will no longer work on the host nodes to see Kubernetes pods. Instead, tools like crictl are used.

Security and Isolation Features

containerd is designed with security as a first-class citizen. It leverages the Open Container Initiative (OCI) runtime specification to ensure that containers are started with the correct security profiles.

Integration with Seccomp and AppArmor

When containerd instructs runc to start a container, it passes along a JSON configuration file (the OCI spec). This file includes settings for:

  • Namespaces: Providing isolation for PID, Network, Mount, and UTS.
  • Control Groups (cgroups): Limiting CPU, Memory, and Disk I/O.
  • Capabilities: Dropping unnecessary root privileges.
  • Security Profiles: Applying Seccomp filters and AppArmor/SELinux profiles.

Because containerd is a focused, minimal daemon, its attack surface is significantly smaller than that of a monolithic container engine. It follows the principle of least privilege, running only the code necessary to manage the container lifecycle.

Technical Hands-on: CTR vs. CRICTL

When working with containerd, developers often encounter two different command-line tools. It is important to understand their specific use cases.

The CTR Tool

The ctr tool is a CLI included with containerd. However, it is important to note that ctr is intended primarily for debugging and testing by containerd developers. It is not a user-friendly replacement for the Docker CLI.

  • Usage: ctr images pull docker.io/library/redis:latest
  • Characteristics: It requires you to be very explicit about namespaces and does not have a "friendly" interface for complex operations.

The CRICTL Tool

If you are operating a Kubernetes cluster, crictl is the recommended tool. It is a CLI for CRI-compatible container runtimes.

  • Usage: crictl ps or crictl pods
  • Characteristics: It is designed to reflect the Kubernetes view of the world. It understands the concept of "Pods" (which containerd itself does not—pods are a higher-level abstraction handled by the CRI plugin).

In our operational workflows, we use crictl for troubleshooting production nodes because it correctly interacts with the CRI namespaces that Kubernetes uses, whereas ctr would require additional flags to see the same data.

Configuration and Tuning

The behavior of containerd is governed by a configuration file, usually located at /etc/containerd/config.toml.

A critical section of this file for many users is the [plugins."io.containerd.grpc.v1.cri"] block. This is where you can configure:

  • Sandbox Image: The "pause" image used by Kubernetes.
  • Registry Mirrors: Configuring containerd to use a local cache or a specific private registry.
  • Max Container Log Size: Preventing a single container from filling up the node's disk with logs.

For high-performance networking, ensure that the bin_dir for CNI (Container Network Interface) plugins is correctly mapped. In our experience, misconfigurations in the config.toml regarding CNI paths are the leading cause of "ContainerCreating" timeouts in new Kubernetes installations.

Conclusion

containerd has transitioned from a supporting player in the Docker ecosystem to the leading industry-standard container runtime. By focusing on robustness, performance, and OCI compliance, it provides the stable foundation necessary for the massive scale of modern cloud-native applications. Whether you are running a single server or a 5,000-node Kubernetes cluster, containerd is likely the engine under the hood, silently managing the complex dance of namespaces, cgroups, and filesystem layers that make containerization possible.

Its modular architecture and the ingenious use of the "Shim" process ensure that our infrastructure is more resilient than ever before. As the industry moves further away from monolithic engines and toward specialized, high-performance components, containerd's importance will only continue to grow.

Frequently Asked Questions

Is containerd the same as Docker?

No. Docker is a full suite of developer tools and a high-level engine, while containerd is a specific component (the runtime) that manages the container lifecycle. Docker actually uses containerd internally to run containers.

Can I run containerd without Docker?

Yes. Many production environments, especially Kubernetes clusters, run containerd as a standalone daemon. This reduces overhead and simplifies the software stack on production nodes.

Does containerd support Windows?

Yes, containerd is available for both Linux and Windows. On Windows, it interfaces with the Host Compute Service (HCS) to manage Windows Server Containers.

How do I see my containers if I am using containerd?

If you are using Kubernetes, use the kubectl command from your local machine. If you are logged into a node, use the crictl ps command. The standard docker ps command will not work if the Docker daemon is not installed or if the containers were started by another client.

Why did Kubernetes move to containerd?

Kubernetes moved to containerd to reduce complexity and improve performance. By removing the need for the dockershim translation layer, Kubernetes can interact directly with the container runtime via the Container Runtime Interface (CRI), leading to better stability and lower resource usage.