
August 20, 2026
9 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
If you manage production Linux infrastructure or Kubernetes clusters, understanding containerd: The Container Runtime Explained is no longer optional academic knowledge—it is an operational requirement. While Docker popularized containers, modern orchestration relies directly on containerd as the stable, lightweight engine that actually executes workloads. For developers building scalable systems, whether for global SaaS platforms or local Nepali enterprise applications, distinguishing between the CLI tooling and the underlying runtime prevents critical debugging blind spots.
When I configure server environments for clients, particularly those moving from legacy monoliths to microservices as discussed in my guide on migrating Laravel monoliths to microservices, the choice of runtime dictates long-term maintainability. Many engineers still conflate the Docker daemon with the actual container execution layer. This confusion leads to bloated production nodes, security surface areas that are difficult to audit, and upgrade paths that break unexpectedly. By stripping away the GUI and build tools, containerd provides a predictable, API-driven foundation that aligns with how modern infrastructure automation actually works.
What is containerd and why does it matter?
At its core, containerd is a daemon process that exposes a gRPC API for managing container lifecycles. It does not build images; it pulls them, unpacks them into snapshots, creates the necessary namespaces and cgroups, and hands off process execution to a compliant OCI runtime like runc. This separation of concerns is the defining characteristic of modern container architecture.
This architecture matters because it decouples innovation at the orchestration layer from stability at the execution layer. When Kubernetes updates its scheduling logic, containerd remains unaffected. When a new storage driver emerges, only the snapshotter component needs updating. For production systems where uptime is non-negotiable, this modularity reduces risk significantly compared to monolithic daemons that bundle networking, building, and execution into a single binary.
How does the Container Runtime Interface work?
The Container Runtime Interface (CRI) is the protocol that allows kubelets to communicate with any compliant runtime without code changes. Before CRI, integrating a new runtime required forking Kubernetes itself. Now, any runtime implementing the CRI gRPC service can plug in seamlessly. This standardization is why containerd has become the default for most distributions.
CRI Implementation Details
In practice, containerd implements two primary CRI services: the ImageService and the RuntimeService. The ImageService handles pulling, listing, and removing container images, while the RuntimeService manages pod sandboxes, container creation, start/stop operations, and exec calls. These services run as plugins within the containerd daemon, configured via the config.toml file.
# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.k8s.io/pause:3.10"
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true A common mistake I encounter during server hardening is leaving the default CRI configuration untouched. On Ubuntu 24.04 LTS servers running systemd, failing to set SystemdCgroup = true causes resource limits to be silently ignored because containerd defaults to cgroupfs driver while systemd expects unified hierarchy management. Always verify this matches your host's init system before deploying workloads.
Pod Sandbox Creation Flow
Understanding the sandbox creation sequence helps diagnose startup failures. When a pod is scheduled, the kubelet first requests a sandbox via RunPodSandbox. containerd allocates a pause container that holds the network namespace open, then configures CNI plugins to attach interfaces. Only after the sandbox reports ready does containerd proceed to create application containers within that shared namespace. If networking fails at this stage, you'll see pods stuck in ContainerCreating state indefinitely.
How do containerd and Docker differ in production?
This distinction causes more confusion than almost any other topic in modern DevOps. Docker Engine includes containerd as a subcomponent but wraps it with additional layers: the Docker daemon (dockerd), the BuildKit builder, volume management, and the familiar CLI. When you run docker run, the request flows through dockerd → containerd → runc. In Kubernetes environments, this extra hop adds complexity without benefit since the cluster manages images, networking, and volumes independently.
| Feature | Docker Engine | containerd Standalone |
|---|---|---|
| Primary Use Case | Developer workstations, CI builds | Production Kubernetes nodes, edge devices |
| Image Building | Built-in (BuildKit) | Not included (use Buildah/Kaniko) |
| Memory Footprint | ~150-300MB idle | ~30-60MB idle |
| Attack Surface | Larger (daemon + builder + CLI) | Minimal (single daemon + shim) |
| Kubernetes Integration | Via dockershim (removed in v1.24+) | Native CRI support |
| Configuration Format | JSON daemon.json | TOML config.toml |
| CLI Tool | docker | ctr (debug), crictl (K8s) |
For teams transitioning from Docker-based workflows, the biggest adjustment is losing the docker command on production nodes. Instead, use crictl for Kubernetes-aware debugging or ctr for direct containerd inspection. I recommend installing both tools on every node—they're invaluable when pods fail to start and logs show nothing useful. As noted in my article on hiring DevOps engineers in Nepal, proficiency with these lower-level tools separates operators who can troubleshoot real incidents from those dependent on dashboard abstractions.
When Docker Still Makes Sense
Docker remains excellent for local development and CI pipelines where image building is required. The ergonomic benefits of docker-compose for multi-service testing outweigh the overhead. However, deploying Docker Engine on production Kubernetes nodes solely because "that's what we've always used" introduces unnecessary risk. The removed dockershim in Kubernetes 1.24 forced many organizations to confront this reality; those who migrated early report fewer node-level incidents and faster security patching cycles.
How do you debug containerd issues effectively?
Debugging containerd requires shifting mental models from interactive shells to API inspection. Since there's no persistent shell session like docker exec provides by default, you must query state through CRI-compatible tools. The crictl utility mirrors Docker CLI semantics while speaking CRI natively, making it the standard diagnostic interface.
Essential Debugging Commands
crictl ps -a: List all containers including stopped ones, showing exit codes and timestamps crucial for crash loop analysiscrictl inspect <container-id>: Retrieve full JSON metadata including mount points, environment variables, and resource limits applied at runtimecrictl logs <container-id>: Stream stdout/stderr directly from the runtime log files, bypassing kubelet aggregation delaysctr -n k8s.io content ls: Inspect raw image blobs stored in containerd's content store, useful for verifying pull integrityjournalctl -u containerd -f: Follow daemon logs with structured output; filter by container ID using grep patterns
# Check if containerd recognizes a specific image
sudo ctr -n k8s.io images ls | grep nginx
# Inspect runtime configuration drift
sudo containerd config dump | grep -A5 'runtimes.runc'
# Verify CNI plugin health
sudo crictl info | jq '.cniconfig' A pattern I've seen repeatedly involves stale container state after unclean shutdowns. When nodes reboot abruptly, containerd may retain references to containers whose processes no longer exist. Running crictl rm --force $(crictl ps -aq) clears orphaned entries safely. Always pair this with checking /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots for leaked filesystem layers that consume disk space invisibly.
Log Aggregation Considerations
containerd writes container logs to /var/log/pods/<namespace>_<pod-name>_<uid>/<container-name>/ as rotated JSON files. Unlike Docker's json-file driver, containerd uses CRI logging format with timestamp and stream fields. Configure your log collector (Fluent Bit, Vector, Promtail) to parse this format explicitly. Misconfigured parsers cause missing logs in observability stacks—a problem I've diagnosed multiple times on client clusters where alerts fired but dashboards showed blank panels.
How should you secure containerd in production?
Security hardening starts with minimizing privileges. containerd supports rootless mode where the daemon runs as an unprivileged user, mapping container UIDs to high-numbered host ranges. This prevents container escapes from gaining root access to the host. Enable it via systemd user units rather than modifying the main service file.
Runtime Security Controls
- Enable SELinux/AppArmor profiles: Default containerd configs disable mandatory access control. Create custom profiles restricting syscalls beyond basic seccomp defaults.
- Restrict image registries: Configure
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]to allowlist approved sources, preventing accidental pulls from public repositories containing malicious payloads. - Disable privileged containers: Set
disable_proc_mount = trueand enforce PodSecurity admission controllers cluster-wide to block privilege escalation vectors. - Audit socket permissions: The CRI socket at
/run/containerd/containerd.sockgrants full runtime control. Restrict ownership to root:root with mode 0660; never expose over TCP. - Rotate credentials regularly: Registry auth tokens stored in
/etc/containerd/certs.d/should follow secret rotation policies aligned with your compliance requirements.
For legal-tech platforms handling sensitive case data, these controls aren't optional extras—they're foundational requirements. When architecting secure client portals as described in my overview of legal tech solutions for law firms, runtime isolation provides defense-in-depth alongside application-layer encryption and access controls. Defense assumes breach; layered containment limits blast radius when vulnerabilities inevitably surface.
Conclusion
Mastering containerd: The Container Runtime Explained transforms how you operate production infrastructure. You move from treating containers as black boxes to understanding the precise mechanisms governing their lifecycle, security boundaries, and failure modes. This knowledge pays dividends during incident response, capacity planning, and security audits—moments when abstraction leaks cost real money and trust.
Start by replacing Docker Engine with standalone containerd on a non-production node. Practice debugging with crictl until the commands feel natural. Review your CRI configuration against current best practices for your Kubernetes version. When you're ready to implement these patterns in your own environment or need expert guidance on container runtime optimization, reach out to discuss your infrastructure needs.

