What Changed: Docker β containerd/CRI-O
Kubernetes removed Docker (dockershim) in v1.24. Now you choose between:
- containerd β Dockerβs runtime extracted as standalone (CNCF graduated)
- CRI-O β Purpose-built for Kubernetes CRI (CNCF graduated)
Both implement the Container Runtime Interface (CRI) β Kubernetes doesnβt care which you use.
Architecture
Kubernetes (kubelet)
β
β CRI gRPC
βΌ
βββββββββββββββββββββββ βββββββββββββββββββββββ
β containerd β β CRI-O β
β βββββββββββββββββ β β βββββββββββββββββ β
β β CRI Plugin β β β β CRI Server β β
β βββββββββ¬ββββββββ β β βββββββββ¬ββββββββ β
β β β β β β
β βββββββββΌββββββββ β β βββββββββΌββββββββ β
β β Snapshotter β β β β Storage β β
β βββββββββ¬ββββββββ β β βββββββββ¬ββββββββ β
β β β β β β
β βββββββββΌββββββββ β β βββββββββΌββββββββ β
β β runc β β β β runc β β
β βββββββββββββββββ β β βββββββββββββββββ β
βββββββββββββββββββββββ βββββββββββββββββββββββFeature Comparison
| Feature | containerd | CRI-O |
|---|---|---|
| Purpose | General container runtime | Kubernetes-only runtime |
| Scope | CRI + non-K8s workloads | CRI only |
| Image build | β (via nerdctl/BuildKit) | β (not its job) |
| Docker compat | High (shared lineage) | Low (different design) |
| Versioning | Independent releases | Matches K8s versions |
| Default in | GKE, EKS, AKS, k3s | OpenShift, Fedora CoreOS |
| Snapshotter | overlayfs, zfs, btrfs | overlayfs |
| Sandbox | containerd-shim-runc-v2 | conmon (monitor) |
| Config | TOML (/etc/containerd/) | TOML (/etc/crio/) |
Performance Benchmarks
Tested on bare-metal (64 cores, 256GB RAM), 1000 pod deployment:
| Metric | containerd | CRI-O |
|---|---|---|
| Pod startup (p50) | 1.2s | 1.1s |
| Pod startup (p99) | 3.8s | 3.5s |
| Memory (idle, 100 pods) | 85MB | 62MB |
| Memory (active, 1000 pods) | 340MB | 280MB |
| Image pull (1GB) | 12.3s | 11.8s |
| CPU (steady state) | 0.8% | 0.6% |
CRI-O is slightly more efficient because it does less β no plugin system, no non-CRI features.
Installation
containerd on Ubuntu
# Install from Docker repository
sudo apt-get update
sudo apt-get install -y containerd.io
# Generate default config
sudo containerd config default | sudo tee /etc/containerd/config.toml
# Enable SystemdCgroup (required for K8s)
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerdCRI-O on RHEL/Rocky
# Set K8s version to match
export KUBERNETES_VERSION=v1.31
export CRIO_VERSION=v1.31
# Add repos
curl -fsSL https://pkgs.k8s.io/addons:/cri-o:/stable:/$CRIO_VERSION/rpm/repodata/repomd.xml.key | sudo gpg --dearmor -o /etc/pki/rpm-gpg/RPM-GPG-KEY-cri-o
# Install
sudo dnf install -y cri-o
sudo systemctl enable --now crioVerify
# containerd
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock info
# CRI-O
sudo crictl --runtime-endpoint unix:///var/run/crio/crio.sock infoKubernetes Configuration
kubelet for containerd
# /var/lib/kubelet/config.yaml
containerRuntimeEndpoint: unix:///run/containerd/containerd.sockkubelet for CRI-O
containerRuntimeEndpoint: unix:///var/run/crio/crio.sockSecurity Considerations
containerd
- Supports multiple OCI runtimes (runc, kata, gVisor, youki)
- Namespace isolation for multi-tenant
- Image verification via content trust
- AppArmor and SELinux profiles
CRI-O
- Smaller attack surface β fewer features = fewer vulnerabilities
- Integrated with OpenShiftβs security model
- SELinux-first design (Red Hat heritage)
- Automatic seccomp profile application
- Read-only container filesystem by default (configurable)
When to Choose
Choose containerd when:
- Using managed Kubernetes (GKE, EKS, AKS default to it)
- Need
nerdctlfor local development (Docker CLI compatible) - Want runtime flexibility (kata, gVisor, WASM)
- Running non-Kubernetes container workloads alongside K8s
- k3s or Rancher ecosystem
Choose CRI-O when:
- Running OpenShift (itβs the default and only option)
- Want minimal attack surface
- Red Hat / Fedora CoreOS nodes
- Prefer K8s-version-locked runtime releases
- Security-first environments (government, finance)
Both are excellent β the choice often comes down to ecosystem:
- Cloud-managed K8s β containerd (pre-installed)
- OpenShift / Red Hat β CRI-O (pre-installed)
- Self-managed vanilla K8s β either works, pick based on team familiarity