
August 20, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Kubernetes nodes do not run your application code directly. They delegate execution to a containerd runtime that pulls images, creates sandboxes, and starts processes through OCI-compliant runtimes like runc. If you operate Linux servers or clusters, confusing Docker CLI tooling with the actual execution layer creates blind spots during outages. This guide explains what containerd does, how the Container Runtime Interface (CRI) connects the kubelet to it, and how to manage the containerd service on production nodes.
When I configure server environments for clients moving from legacy monoliths to microservices—as covered in my guide on migrating Laravel monoliths to microservices—the runtime choice affects long-term maintainability. Many engineers still treat the Docker daemon as the execution layer. That confusion leads to bloated nodes, wider attack surfaces, and upgrade paths that break without warning. Stripping away GUI and build tooling, containerd provides a predictable API-driven foundation aligned with how modern Linux server administration actually works.
What is the containerd runtime and why does it matter?
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 namespaces and cgroups, and hands process execution to a compliant OCI runtime such as runc. Docker Engine embeds containerd internally, but Kubernetes nodes typically run containerd standalone to avoid an extra daemon hop.
The project graduated as a CNCF project and ships as the default runtime on most Kubernetes distributions. Major cloud providers—AWS EKS, Google GKE, Azure AKS—all rely on containerd on worker nodes. Version 2.x introduced a rewritten API surface and improved snapshotter plugins. Check your node with containerd --version during any cluster upgrade planning.
This separation decouples orchestration innovation from execution stability. When Kubernetes updates 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 compared to monolithic daemons that bundle networking, building, and execution into one binary.
On a real client project running booking workloads on Kubernetes—similar to platforms like Adventure Third Pole Trek—understanding this stack shortened incident response. Operators who know where the kubelet ends and containerd begins fix ContainerCreating stalls faster than teams searching Docker logs that do not exist on the node.
How does the Container Runtime Interface connect Kubernetes to containerd?
The Container Runtime Interface (CRI) is the gRPC protocol that lets kubelets communicate with any compliant runtime without code changes. Before CRI, integrating a new runtime required forking Kubernetes itself. Now any runtime implementing the CRI service plugs in directly. This standardization is why the containerd runtime became the default for most distributions after dockershim removal in Kubernetes 1.24.
CRI services inside containerd
containerd implements two primary CRI services as plugins. The ImageService handles pulling, listing, and removing container images. The RuntimeService manages pod sandboxes, container creation, start and stop operations, and exec calls. Both run inside the containerd daemon and are configured through /etc/containerd/config.toml.
# /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 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. containerd defaults to the cgroupfs driver while systemd expects unified hierarchy management. Always verify this matches your host init system before deploying workloads. The official Kubernetes container runtime documentation covers the required settings per distribution.
Pod sandbox creation flow
When a pod is scheduled, the kubelet first requests a sandbox via RunPodSandbox. containerd starts a pause container that holds the network namespace open. CNI plugins then attach interfaces. Only after the sandbox reports ready does containerd create application containers within that shared namespace. If networking fails here, pods stay stuck in ContainerCreating 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), BuildKit, volume management, and the familiar CLI. When you run docker run, the request flows through dockerd → containerd → runc. In Kubernetes, that 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 or Kaniko |
| Memory footprint | ~150–300 MB idle | ~30–60 MB idle |
| Attack surface | Larger (daemon + builder + CLI) | Minimal (daemon + shim) |
| Kubernetes integration | Via dockershim (removed in v1.24+) | Native CRI support |
| Configuration | JSON daemon.json | TOML config.toml |
| CLI tools | docker | ctr (debug), crictl (K8s) |
For teams transitioning from Docker-based workflows, the biggest adjustment is losing the docker command on production nodes. Use crictl for Kubernetes-aware debugging or ctr for direct containerd inspection. I recommend installing both on every node. As noted in my article on hiring DevOps engineers in Nepal, proficiency with these lower-level tools separates operators who 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 on developer laptops. See our guides on containerizing a Laravel app and installing Docker on Ubuntu for workstation setup. However, deploying Docker Engine on production Kubernetes nodes solely because "that is what we have always used" introduces unnecessary risk. Organizations that migrated early report fewer node-level incidents and faster security patching cycles.
How do you manage the containerd service on Linux?
The containerd service is a systemd unit on most Linux distributions. It starts at boot, listens on a Unix socket at /run/containerd/containerd.sock, and stores state under /var/lib/containerd/. Understanding service management is essential because a stopped or misconfigured daemon blocks every pod on the node.
systemd commands for the containerd service
# Check containerd service status
sudo systemctl status containerd
# Restart after config.toml changes
sudo systemctl restart containerd
# Enable at boot
sudo systemctl enable containerd
# Follow daemon logs
sudo journalctl -u containerd -f --no-pager After editing config.toml, always validate syntax before restarting. Run sudo containerd config dump to print the effective configuration including defaults you did not explicitly set. A malformed TOML file prevents the containerd service from starting, which immediately marks the node as NotReady in Kubernetes. For broader systemd patterns, see our guide on managing services on Linux.
Key paths and sockets
/etc/containerd/config.toml— main configuration file/run/containerd/containerd.sock— gRPC and CRI socket (mode 0660, root-only)/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/— filesystem layers/var/log/pods/— CRI-format container logs written by the kubelet/etc/cni/net.d/— CNI plugin configuration consumed during sandbox setup
On nodes I maintain through enterprise application deployments, I add a systemd drop-in only when needed—for example, raising file descriptor limits on high-density nodes. Avoid unnecessary overrides. The stock unit file from your distribution vendor is tested against their kernel and cgroup configuration.
How do you debug containerd pod failures effectively?
Debugging the containerd runtime requires shifting from interactive shells to API inspection. Since there is no persistent shell session like docker exec provides by default, you query state through CRI-compatible tools. The crictl utility mirrors Docker CLI semantics while speaking CRI natively.
Essential debugging commands
crictl ps -a— list all containers including stopped ones with exit codescrictl inspect <container-id>— full JSON metadata including mounts and limitscrictl logs <container-id>— stream stdout and stderr from runtime log filesctr -n k8s.io content ls— inspect raw image blobs in the content storejournalctl -u containerd -f— follow daemon logs with structured output
# Check if containerd recognizes a specific image
sudo ctr -n k8s.io images ls | grep nginx
# Inspect runtime configuration
sudo containerd config dump | grep -A5 'runtimes.runc'
# Verify CNI plugin health
sudo crictl info | jq '.config.storageRoot' A pattern I have 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 check snapshot directories for leaked filesystem layers that consume disk space invisibly. For broader cluster diagnostics, see our Kubernetes troubleshooting field guide and CrashLoopBackOff debugging guide.
Log format and aggregation
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, or Promtail—to parse this format explicitly. When inspecting raw JSON locally, a JSON formatter helps read nested CRI log entries quickly.
How should you secure the containerd runtime 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.
Runtime security controls
- Enable AppArmor or SELinux profiles: Default configs may disable mandatory access control. Create custom profiles restricting syscalls beyond basic seccomp defaults.
- Restrict image registries: Configure registry mirrors and allowlists in
config.tomlto prevent pulls from untrusted sources. - Block privileged containers: Enforce Pod Security admission controllers cluster-wide to block privilege escalation vectors.
- Audit socket permissions: The CRI socket grants full runtime control. Keep ownership at root:root with mode 0660. Never expose it over TCP.
- Scan images before deploy: Integrate Trivy image scanning into your CI pipeline before images reach production nodes.
For legal-tech platforms handling sensitive case data, these controls are 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. Defense assumes breach; layered containment limits blast radius when vulnerabilities surface. The official containerd documentation covers rootless setup and plugin configuration in detail.
Key Takeaways
- The containerd runtime executes containers on Kubernetes nodes—it is not a replacement for Docker on your laptop.
- CRI is the gRPC bridge between kubelet and containerd; misconfigured
SystemdCgroupsilently breaks resource limits. - Manage the containerd service with
systemctl; validateconfig.tomlwithcontainerd config dumpbefore restarting. - Use
crictlon production nodes instead ofdocker—it speaks CRI natively and shows Kubernetes-aware state. - Debug pod failures by checking whether crictl sees the container, then inspect exit codes and containerd service logs.
- Harden nodes with registry allowlists, Pod Security policies, image scanning, and restricted CRI socket permissions.
People Also Ask
Is containerd the same as Docker?
No. Docker Engine includes containerd internally but adds dockerd, BuildKit, and a developer CLI on top. Kubernetes nodes run containerd standalone and talk to it directly through CRI, skipping dockerd entirely. You build images with Docker or Buildah in CI; you run them with containerd on the node.
What is the containerd service and how do I restart it?
The containerd service is a systemd unit that runs the containerd daemon. Restart it with sudo systemctl restart containerd after editing /etc/containerd/config.toml. Always run containerd config dump first to confirm syntax. A failed restart marks the Kubernetes node as NotReady.
Can I use docker commands on a Kubernetes node running containerd?
Not reliably. Production Kubernetes nodes typically have no Docker daemon installed. Use crictl for pod and container operations, or ctr -n k8s.io for low-level containerd inspection. Install crictl alongside your cluster tooling on every node.
Why did Kubernetes remove dockershim?
dockershim was a maintenance burden that added an unnecessary translation layer between the kubelet and Docker's containerd instance. CRI provides a standard interface any runtime can implement. containerd, CRI-O, and others now plug in directly, simplifying node architecture and reducing attack surface.
Next Steps for Your Infrastructure
Understanding the containerd runtime transforms how you operate production infrastructure. You move from treating containers as black boxes to knowing the precise mechanisms governing lifecycle, security boundaries, and failure modes. That knowledge pays off during incident response, capacity planning, and security audits.
Start by confirming containerd is your node runtime with crictl info. Review your CRI configuration against current best practices for your Kubernetes version. Practice debugging with crictl on a staging node until the commands feel natural. If you are deploying your first cluster, our Kubernetes basics guide and GitLab CI pipeline tutorial cover the full path from image build to node execution.
When you need help hardening production nodes or migrating from Docker-based workflows, contact us to discuss your infrastructure requirements. You can also reach out directly for a technical review of your current container runtime setup.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

