Skip to main content
πŸŽ“ Claude Code Masterclass Learn AI-assisted development on Udemy β€” plus the companion book on Leanpub & Amazon. Start Learning
CRI-O vs containerd: Kubernetes Container Runtime Guide
Platform Engineering

CRI-O vs containerd: Kubernetes Container Runtime Guide

CRI-O vs containerd β€” architecture differences, performance benchmarks, security posture, OCI compliance, and which runtime to choose for production Kubernetes.

LB
Luca Berton
Β· 2 min read

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

FeaturecontainerdCRI-O
PurposeGeneral container runtimeKubernetes-only runtime
ScopeCRI + non-K8s workloadsCRI only
Image buildβœ… (via nerdctl/BuildKit)❌ (not its job)
Docker compatHigh (shared lineage)Low (different design)
VersioningIndependent releasesMatches K8s versions
Default inGKE, EKS, AKS, k3sOpenShift, Fedora CoreOS
Snapshotteroverlayfs, zfs, btrfsoverlayfs
Sandboxcontainerd-shim-runc-v2conmon (monitor)
ConfigTOML (/etc/containerd/)TOML (/etc/crio/)

Performance Benchmarks

Tested on bare-metal (64 cores, 256GB RAM), 1000 pod deployment:

MetriccontainerdCRI-O
Pod startup (p50)1.2s1.1s
Pod startup (p99)3.8s3.5s
Memory (idle, 100 pods)85MB62MB
Memory (active, 1000 pods)340MB280MB
Image pull (1GB)12.3s11.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 containerd

CRI-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 crio

Verify

# containerd
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock info

# CRI-O
sudo crictl --runtime-endpoint unix:///var/run/crio/crio.sock info

Kubernetes Configuration

kubelet for containerd

# /var/lib/kubelet/config.yaml
containerRuntimeEndpoint: unix:///run/containerd/containerd.sock

kubelet for CRI-O

containerRuntimeEndpoint: unix:///var/run/crio/crio.sock

Security 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 nerdctl for 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

Free 30-min AI & Cloud consultation

Book Now