containerd vs CRI-O
Both are high-level runtimes. Both can run containers for Kubernetes. The difference is philosophy.
containerd: Swiss Army Knife
Built for everyone — Docker, Kubernetes, standalone use, embedded systems.
containerd features:
├── Full image management
├── Content-addressable storage
├── Multiple snapshot drivers (overlayfs, btrfs, zfs)
├── Plugin architecture
├── Namespace isolation (multi-tenant)
├── Works without Kubernetes
└── gRPC API for custom integrations
History
- 2017: Docker extracted containerd from their monolithic codebase
- 2017: Donated to Cloud Native Computing Foundation (CNCF)
- 2019: Graduated project status (mature, production-ready)
- Today: Used by Docker, Kubernetes, AWS, Google, Azure
Key Characteristics
- General-purpose - Can be used standalone or with any orchestrator
- Feature-rich - Comprehensive image and storage management
- Extensible - Plugin system for custom functionality
- Widely adopted - Industry standard
CRI-O: Kubernetes-Only
Built by Red Hat specifically for Kubernetes. Does nothing more than what K8s needs.
CRI-O features:
├── CRI (Container Runtime Interface) implementation
├── Image pulling
├── Container lifecycle
└── That's it — if K8s doesn't need it, CRI-O doesn't have it
Motto: "Boring is good" — minimal surface area, fewer bugs
History
- 2016: Red Hat started CRI-O project
- 2017: First stable release aligned with Kubernetes v1.7
- Today: Default runtime in OpenShift (Red Hat’s Kubernetes distribution)
Key Characteristics
- Kubernetes-focused - Only implements CRI, nothing else
- Minimal - Smaller codebase, fewer features
- Stable - Less code = fewer bugs
- OpenShift default - Tight integration with Red Hat ecosystem
Architecture Comparison
KUBERNETES
│
CRI (gRPC API)
│
┌────────────┴────────────┐
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ containerd │ │ CRI-O │
│ │ │ │
│ • Image management│ │ • Image management│
│ • Snapshots │ │ • Minimal features│
│ • Content store │ │ • K8s-focused only│
│ • Multi-tenant │ │ │
│ • Plugin system │ │ │
└─────────┬─────────┘ └─────────┬─────────┘
│ │
▼ ▼
┌───────────────────────────────────────────────┐
│ OCI Runtime (runc) │
│ (or crun, youki, kata, gvisor) │
└───────────────────────────────────────────────┘
Both ultimately call the same low-level runtime. The high-level runtime is just the manager.
Feature Comparison
| Feature | containerd | CRI-O |
|---|---|---|
| Primary Use Case | General-purpose | Kubernetes only |
| Image Management | Full-featured | Basic, sufficient |
| Storage Drivers | Multiple (overlayfs, btrfs, zfs) | overlayfs primary |
| Standalone Usage | Yes, via ctr or nerdctl CLI | No |
| Plugin System | Yes, extensible | No |
| Multi-tenancy | Namespace isolation | Not needed (K8s handles) |
| Default OCI Runtime | runc | crun (can use runc) |
| API | gRPC (general) | CRI gRPC only |
| Codebase Size | Larger | Smaller |
| Maintenance | CNCF + community | Red Hat + community |
Performance
Startup Time
CRI-O has slightly faster pod startup in some benchmarks:
- Smaller codebase
- Less overhead
- Uses crun (faster than runc)
containerd is still very fast:
- Difference is milliseconds in practice
- Negligible for most workloads
Resource Usage
Both are lightweight:
Idle resource usage:
├── containerd: ~30-50 MB RAM
└── CRI-O: ~20-40 MB RAM
Difference is minimal on modern systems.
Who Uses What
| Platform | High-Level Runtime | Low-Level Runtime |
|---|---|---|
| Docker Desktop | containerd | runc |
| Kubernetes (default) | containerd | runc |
| OpenShift (Red Hat) | CRI-O | crun |
| GKE (Google) | containerd | runc |
| EKS (AWS) | containerd | runc |
| AKS (Azure) | containerd | runc |
Everyone except Red Hat converged on containerd + runc.
Why CRI-O Prefers crun
CRI-O defaults to crun instead of runc. Here’s why:
| Feature | runc | crun |
|---|---|---|
| Language | Go | C |
| Startup time | Good | Faster |
| Memory usage | Higher | Much lower |
| systemd integration | Good | Excellent |
| cgroup v2 | Supported | Native-first |
| Pod density | Good | Better |
For high-density Kubernetes nodes (many pods per node), crun’s lower memory footprint and faster startup add up. Red Hat optimizes for this in OpenShift.
When to Choose containerd
Use containerd if:
- Running Docker - containerd is Docker’s runtime
- Need standalone usage - Want to use
ctrornerdctlCLI - Custom integrations - Building on top of container runtime
- Plugin ecosystem - Need extensibility
- Industry standard - Want widest compatibility
When to Choose CRI-O
Use CRI-O if:
- OpenShift - It’s the default and best integrated
- Minimalism - Want smallest possible runtime
- Kubernetes-only - Don’t need standalone features
- Red Hat ecosystem - Tight integration with RHEL
- Security-focused - Smaller codebase = smaller attack surface
The CRI (Container Runtime Interface)
The gRPC interface that Kubernetes (kubelet) uses to talk to container runtimes. Both containerd and CRI-O implement CRI — so switching is theoretically possible (drain node → swap runtime → uncordon), and your images remain compatible due to OCI compliance. However, in practice it’s rarely done mid-cluster due to different storage paths, logging configurations, and default settings between runtimes. Most teams pick one at cluster creation and stick with it.
Cloud Providers Lock You In: Managed Kubernetes (GKE, EKS, AKS) does not let you change the runtime — containerd is the only option. OpenShift locks you into CRI-O. Only with self-managed Kubernetes (kubeadm, bare-metal) do you have full control over runtime choice.
Key Takeaways
- containerd = general-purpose - Works with Docker, K8s, standalone
- CRI-O = Kubernetes-only - Minimal, focused implementation
- Both are production-ready - Used by major companies
- Performance is similar - Negligible differences in practice
- containerd is more popular - Used by all major cloud providers
- CRI-O is OpenShift default - Best for Red Hat ecosystem
- Both use same OCI runtimes - runc/crun underneath
- Choice doesn’t affect workloads - OCI compliance ensures compatibility
Comments