containerd: The Container Runtime Explained

Khimananda Oli 8 min read Database
containerd: The Container Runtime Explained

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.

KubeletNode AgentCRI (gRPC)containerdImage ServiceRuntime ServiceSnapshotter / DiffShim APIrunc / shimOCI Process
Kubelet communicates with containerd over CRI gRPC; containerd delegates process execution to runc via a persistent shim.

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.

  1. Install crictl: Download the latest release from the kubernetes-sigs/cri-tools GitHub repository. Place the binary in /usr/local/bin and make it executable.
  2. Configure the endpoint: Create /etc/crictl.yaml with runtime-endpoint: unix:///run/containerd/containerd.sock. Without this, crictl defaults to dockershim paths and fails silently.
  3. Inspect pod sandboxes: Use crictl pods to list sandbox containers. Every pod has a pause container that holds the network namespace; application containers join this namespace.
  4. 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.
  5. Check image cache: crictl images shows locally cached images with digests. Useful for verifying whether a node pulled the correct tag after a deployment rollback.
crictl psList containerscrictl inspectRuntime metadatacrictl logsStream stdout/stderrcontainerdContent StoreSnapshotterTask ManagerEvent PublisherLease / GCshim-runc-v2Per-container parentruncOCI executorCNI PluginNetwork attach
crictl commands map directly to containerd services; the shim isolates container processes from daemon restarts.

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:

CriteriacontainerdDocker EngineCRI-O
Kubernetes nativeYes (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 toolingcrictl (debug only)docker (full dev experience)crictl (debug only)
Image buildingNo (use BuildKit externally)Yes (built-in)No (use Buildah/Podman)
Attack surfaceMinimal (no build/API server)Larger (daemon + API + builder)Minimal (K8s-scoped only)
Ecosystem supportBroadest (all cloud providers)Legacy (declining in K8s)Strong in RHEL/OpenShift
Best forGeneral-purpose K8s nodesLocal development onlyOpenShift / 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.

config.tomlSystemdCgroup = trueRegistry MirrorsRuntime HandlersMax Concurrent DownloadsDiscard Unpacked Layerscontainerd Daemoncgroup v2 integrationPull from nearest mirrorgvisor / kata selectionParallel layer fetchGC disk pressure reliefStable PodsNo OOM / No Pull FailsFaster DeploysCached + ParallelAudit ReadyPredictable Behavior
Production config.toml settings directly influence stability, deploy speed, and audit readiness of containerd-managed workloads.

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.

Frequently Asked Questions

containerd is a lightweight, industry-standard container runtime focused solely on container lifecycle management. Unlike Docker, it lacks image building or CLI features, reducing attack surface and resource overhead for production Kubernetes clusters in 2026.

Yes.

Run apt update followed by apt install containerd.io from the official Docker repository. Generate default config with containerd config default > /etc/containerd/config.toml, enable systemd cgroup driver, then restart the service using systemctl.

Both are CRI-compliant runtimes, but containerd supports broader non-Kubernetes use cases like edge computing and serverless. CRI-O targets Kubernetes exclusively with tighter integration. Choose containerd for multi-purpose runtime needs in 2026 hybrid cloud deployments.

Edit /etc/containerd/config.toml and set SystemdCgroup = true under [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]. This aligns containerd with kubelet expectations on modern Linux distributions running systemd as init.

Yes. Configure registry mirrors and authentication in /etc/containerd/hosts.d/ using host-specific TOML files. Add credentials via ctr login or embed them in registry config. Supports TLS verification and bearer token auth for enterprise artifact repositories.

Kubelet may still reference dockershim socket. Update --container-runtime-endpoint flag to unix:///run/containerd/containerd.sock in kubelet configuration. Verify runtime status with crictl info and ensure CNI plugins are correctly configured for the new runtime environment.

Check journalctl -u containerd for errors. Validate config syntax with containerd config dump. Ensure AppArmor or SELinux policies allow execution. Confirm runc binary exists at configured path and kernel modules like overlayfs are loaded properly.

Yes.

Use ctr for low-level debugging and crictl for Kubernetes-aligned operations. nerdctl provides Docker-compatible CLI experience including build and compose. Install these separately since containerd intentionally excludes user-facing tooling to maintain minimal runtime footprint.

Enable Seccomp profiles and AppArmor confinement in config.toml. Restrict capabilities via runtime options. Use read-only root filesystems and drop all unnecessary Linux capabilities. Integrate with image signing verification using sigstore cosign for supply chain security compliance.

Yes. Export images with docker save and import via ctr or nerdctl. Container configurations remain compatible since both use OCI standards. Update orchestration endpoints and verify volume mounts, networking, and logging drivers function identically post-migration testing.

containerd 2.x series is recommended for Kubernetes 1.32 in 2026. It includes improved CRI v1 support, enhanced sandbox isolation, and native sidecar container handling. Always match minor versions per Kubernetes compatibility matrix documentation.

Enable Prometheus endpoint in config.toml under metrics section. Expose /v1/metrics on localhost port 1338. Scrape with Prometheus and visualize container counts, image pulls, task latency, and gRPC errors using official Grafana dashboards for operational visibility.

Absolutely.