
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
If you manage Kubernetes clusters or build CI/CD pipelines, understanding containerd: The Container Runtime Explained is no longer optional—it is foundational. Since Kubernetes v1.24 removed direct Docker support, containerd has become the default low-level runtime for most production environments, yet many engineers still confuse it with the Docker CLI they use locally. This guide strips away the abstraction to show exactly how containerd manages container lifecycles, integrates with the Container Runtime Interface (CRI), and differs from the tools in your daily workflow.
What is containerd and why did Kubernetes adopt it?
At its core, containerd is a daemon process that runs as a systemd service on Linux nodes. It does not build images; it pulls them from registries, unpacks them into snapshots, creates OCI-compliant bundles, and hands off process execution to a lower-level shim. When we discuss deploying applications to Kubernetes, we are ultimately asking containerd to perform these atomic operations reliably under load.
The shift to containerd was driven by modularity. Docker Engine includes many components irrelevant to orchestration: a build engine, a GUI dashboard, and a developer-focused CLI. Kubernetes only needs the runtime. By adopting containerd directly, clusters eliminate an extra translation layer (dockershim), reduce memory footprint per node, and shrink the attack surface—a critical consideration when preparing infrastructure for security hardening and compliance audits. In 2026, every major managed Kubernetes service (EKS, AKS, GKE) uses containerd as the default node runtime.
How does the Container Runtime Interface work?
The Container Runtime Interface (CRI) is a protobuf-based gRPC API that defines two primary services: ImageService and RuntimeService. Kubelet acts as the client; containerd acts as the server. This contract allows Kubernetes to remain agnostic to the underlying implementation—you could theoretically swap containerd for CRI-O without changing cluster configuration.
Image pulling and snapshot management
When a pod schedules to a node, kubelet sends a PullImage request. containerd checks its local content store; if missing, it pulls layers from the registry using authenticated HTTPS. Each layer is unpacked into a filesystem snapshot using a snapshotter (overlayfs is standard on Linux). These snapshots are stacked read-only, with a writable top layer for the container. This copy-on-write approach means ten containers sharing the same base image consume disk space for only one copy of that base.
Container creation and the shim pattern
Critical to understanding containerd: The Container Runtime Explained is the shim architecture. When containerd receives a CreateContainer + StartContainer call, it spawns a separate containerd-shim-runc-v2 process for each container. This shim becomes the parent process of the actual container workload. Why? If the containerd daemon restarts during an upgrade or crash, running containers are unaffected because their parent shim persists. The shim also collects exit codes and forwards I/O back to containerd asynchronously.
// Verify containerd is responding to CRI requests
sudo crictl info
// List all running containers managed by containerd
sudo crictl ps -a
// Inspect a specific container's runtime state
sudo crictl inspect <container-id> Networking and storage plugins
containerd does not implement networking itself. It invokes CNI plugins (Calico, Cilium, Flannel) during container setup to attach interfaces and apply policies. Similarly, CSI drivers handle volume mounts before containerd starts the process. This separation keeps the runtime focused and lets networking evolve independently. For teams evaluating eBPF-based networking with Cilium, this plugin model is what makes advanced traffic control possible without modifying the runtime.
How do you debug containers with crictl?
The most common mistake I see in production incidents is engineers trying to use docker ps on Kubernetes nodes. That command fails because Docker isn't running. You must use crictl, the official CRI-compatible CLI maintained by the Kubernetes community. Install it once, configure the endpoint, and you have full visibility.
- Install crictl: Download the latest release from the kubernetes-sigs/cri-tools GitHub repository. Place the binary in
/usr/local/binand make it executable. - Configure the endpoint: Create
/etc/crictl.yamlwithruntime-endpoint: unix:///run/containerd/containerd.sock. Without this, crictl defaults to dockershim paths and fails silently. - Inspect pod sandboxes: Use
crictl podsto list sandbox containers. Every pod has a pause container that holds the network namespace; application containers join this namespace. - Stream logs directly:
crictl logs -f <container-id>streams stdout/stderr without needing kubectl access—essential when the API server is unreachable during network partitions. - Check image cache:
crictl imagesshows locally cached images with digests. Useful for verifying whether a node pulled the correct tag after a deployment rollback.
A practical tip from incident response: when a pod is stuck in ContainerCreating, check crictl pods first. If the sandbox exists but application containers don't, the issue is usually image pull failure or volume mount timeout—not scheduling. This distinction saves hours of misdirected troubleshooting.
How does containerd compare to Docker and CRI-O?
Choosing a runtime matters for operational complexity, security posture, and team familiarity. Here is how the three dominant options compare in 2026 production environments:
| Criteria | containerd | Docker Engine | CRI-O |
|---|---|---|---|
| Kubernetes native | Yes (default since v1.24) | No (requires Mirantis cri-dockerd) | Yes (Red Hat/OpenShift default) |
| Memory overhead | ~30–50 MB per node | ~150–300 MB per node | ~25–40 MB per node |
| CLI tooling | crictl (debug only) | docker (full dev experience) | crictl (debug only) |
| Image building | No (use BuildKit externally) | Yes (built-in) | No (use Buildah/Podman) |
| Attack surface | Minimal (no build/API server) | Larger (daemon + API + builder) | Minimal (K8s-scoped only) |
| Ecosystem support | Broadest (all cloud providers) | Legacy (declining in K8s) | Strong in RHEL/OpenShift |
| Best for | General-purpose K8s nodes | Local development only | OpenShift / strict compliance |
In practice, containerd wins for most teams because it balances minimalism with broad compatibility. CRI-O is excellent but ties you closer to Red Hat's ecosystem. Docker remains unmatched for local development—keep it on laptops, remove it from nodes. For teams setting up local Kubernetes testing environments, kind actually uses containerd internally, mirroring production behavior more accurately than Docker Desktop's legacy backend.
How do you configure containerd for production workloads?
Default containerd configuration works for basic deployments, but production systems need tuning. The config lives at /etc/containerd/config.toml. Always generate a fresh default before editing:
// Generate full default config
containerd config default > /etc/containerd/config.toml
// Enable SystemdCgroup (required for most K8s distros)
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
// Restart to apply
systemctl restart containerd Mirror registries and air-gapped environments
For Nepal-based deployments or any environment with limited international bandwidth, configure registry mirrors to pull from regional caches or private Harbor instances. Add mirror entries under [plugins."io.containerd.grpc.v1.cri".registry.mirrors]. This prevents image pull timeouts during peak hours and reduces egress costs significantly.
Sandbox and runtime classes
containerd supports multiple runtime handlers. Define a gvisor or kata-containers handler for untrusted workloads, then reference it via Kubernetes RuntimeClass. This lets you run sandboxed containers alongside standard runc containers on the same node—critical for multi-tenant platforms where isolation requirements vary per workload.
Observability and metrics
Enable the Prometheus metrics endpoint in config.toml under [plugins."io.containerd.grpc.v1.cri".metrics]. Expose port 1338 and scrape it with your monitoring stack. Key metrics include containerd_cri_image_pull_duration_seconds, containerd_cri_container_running_count, and snapshotter operation latency. These signals are invaluable when correlating slow deployments with runtime bottlenecks rather than application code. Pair this with Prometheus fundamentals to build dashboards that distinguish runtime issues from app-level problems.
Moving forward with containerd
Mastering containerd: The Container Runtime Explained means moving beyond treating it as invisible infrastructure. Configure it deliberately, debug it confidently with crictl, and monitor its internal metrics as rigorously as your application code. The runtime is where theoretical orchestration meets physical reality—get this layer right, and everything above it becomes more predictable, secure, and efficient. If your team needs help auditing runtime configurations, migrating from Docker, or designing compliant container infrastructure, reach out to discuss your specific environment.