Every container you run — whether it’s a web server, database, or microservice — depends on a container runtime underneath. The runtime is the low-level software that actually creates, manages, and tears down containers on your host system.
For most of docker’s history, the runtime was bundled together with the CLI, networking, and image management into one monolithic tool. But the ecosystem has since split into specialized, modular components. Today, three OCI-compliant runtimes dominate the self-hosted landscape: containerd, CRI-O, and Podman.
Choosing the right onkubernetes It affects your Kubernetes compatibility, security posture, operational complexity, and whether you can run containers without root privileges.
For related reading, see our Kubernetes distribution comparison for runtime integration details, our container build tools guide for image creation workflows, and our container registry comparison for image storage options.
Why Run Your Own Container Runtime?
Cloud providers abstract the container runtime away from you. But when you self-host, you own the full stack — and the runtime choice has real consequences:
- Security: Rootless container execution eliminates entire classes of privilege escalation attacks
- Resource efficiency: Lightweight runtimes use less RAM and CPU on the host, leaving more for your workloads
- Compliance: Some regulated environments require you to control every layer of the infrastructure stack
- Vendor independence: Avoiding Docker Desktop licensing fees and vendor lock-in
- Kubernetes compatibility: Different runtimes integrate differently with the kubelet via the Container Runtime Interface (CRI)
Understanding these trade-offs starts with knowing what each runtime is designed to do.
What Is containerd?
containerd is the industry-standard container runtime that powers Docker, Kubernetes, and countless other projects. Originally extracted from Docker’s internals, it became a standalone CNCF graduated project and is now maintained by the Cloud Native Computing Foundation.
GitHub stats (April 2026):
- ⭐ 20,594 stars
- 🍴 3,875 forks
- 📅 Last active: April 17, 2026
- 📄 License: Apache-2.0
containerd sits between high-level orchestrators and low-level runtimes like runc. It handles image management, container lifecycle, storage, and networking — but deliberately avoids the CLI and Compose layers. It’s designed as a daemon that other tools talk to via gRPC.
Key Features
- CNCF Graduated status with enterprise-grade stability
- CRI plugin built in — provides the Kubernetes Container Runtime Interface out of the box
- Namespace isolation — multiple tenants can share one containerd daemon safely
- Snapshotter plugins — supports overlayfs, btrfs, zfs, and native snapshotters
- NRI (Node Resource Interface) — allows third-party plugins to hook into container lifecycle events
- Content-addressable storage — images are stored and deduplicated efficiently
Installing containerd
On Ubuntu/Debian:
| |
Generate the default configuration:
| |
Verify installation:
| |
Using containerd with Kubernetes
containerd is the default runtime for most Kubernetes distributions. The CRI plugin exposes the necessary endpoints automatically:
| |
For interacting with containerd directly (without Docker), use the ctr CLI:
| |
Note that ctr is a debug/admin tool, not a production workflow tool. For day-to-day operations, most users interact with containerd through Docker, Kubernetes, or higher-level tooling.
What Is CRI-O?
CRI-O is a lightweight container runtime built specifically for Kubernetes. Developed by Red Hat and part of the Kubernetes incubator projects, it implements only the Kubernetes Container Runtime Interface — nothing more, nothing less.
GitHub stats (April 2026):
- ⭐ 5,601 stars
- 🍴 1,161 forks
- 📅 Last active: April 17, 2026
- 📄 License: Apache-2.0
Where containerd tries to be a general-purpose runtime, CRI-O is laser-focused on being the best possible runtime for Kubernetes. It strips away everything that isn’t needed for CRI compliance, resulting in a smaller attack surface and simpler operational model.
Key Features
- Kubernetes-native — purpose-built for the CRI specification
- Minimal attack surface — no Docker API compatibility, no general-purpose container management
- Multiple OCI runtimes — supports runc, crun, and Kata Containers via configuration
- Image pulling from any registry — supports Docker, OCI, and signed images with sigstore
- Integrated with OpenShift — the default runtime for Red Hat’s Kubernetes distribution
- CRI stats API — exposes container and pod metrics for monitoring
Installing CRI-O
On Ubuntu/Debian:
| |
Verify installation:
| |
Configuring CRI-O
The main configuration file is /etc/crio/crio.conf:
| |
Using CRI-O with crun (a faster C alternative to runc):
| |
What Is Podman?
Podman (POD MANager) is a daemonless, rootless container engine developed by Red Hat as part of the containers project. Unlike containerd and CRI-O, Podman provides both the runtime AND the CLI in a single tool — designed as a drop-in replacement for the Docker CLI.
GitHub stats (April 2026):
- ⭐ 31,432 stars
- 🍴 3,062 forks
- 📅 Last active: April 17, 2026
- 📄 License: Apache-2.0
Podman’s most distinctive feature is that it runs without a daemon. Every podman run command spawns a direct process tree — no background service to manage, no single point of failure. It also supports rootless containers out of the box using user namespaces.
Key Features
- Daemonless architecture — no persistent daemon; containers are direct child processes
- Rootless by default — runs containers as non-root users via user namespaces (userns)
- Docker CLI compatible —
alias docker=podmanworks for most workflows - Pod management — groups containers into pods (shared network namespace), similar to Kubernetes pods
- Quadlet integration — systemd-native container management via
.containerand.podfiles - Podman Compose — Docker Compose file compatibility via a separate tool
- Buildah integration — shares the image build tool from the same project family
Installing Podman
On Ubuntu/Debian:
| |
On Fedora/RHEL (usually pre-installed):
| |
Verify installation:
| |
Running Containers with Podman
Basic usage mirrors Docker exactly:
| |
Using Quadlet for Systemd Integration
Quadlet lets you manage containers as native systemd units — the most robust way to run production containers on a self-hosted server:
| |
Enable and start:
| |
This replaces Docker Compose restart policies and supervisor processes with systemd’s battle-tested service management.
Comparison: containerd vs CRI-O vs Podman
| Feature | containerd | CRI-O | Podman |
|---|---|---|---|
| Primary use case | General-purpose runtime, Kubernetes default | Kubernetes-only runtime | Developer workstation, daemonless servers |
| Daemon required | Yes (containerd daemon) | Yes (crio daemon) | No (daemonless) |
| Rootless support | No | Partial (via crun) | Yes (native, default) |
| Kubernetes CRI | Yes (built-in CRI plugin) | Yes (purpose-built for CRI) | Via Podman kube play / kubelet integration |
| Docker CLI compatibility | No (via Docker daemon) | No | Yes (alias docker=podman) |
| Compose support | Via Docker Compose | No | Via podman-compose |
| Pod management | No | Via Kubernetes pods | Yes (native pod concept) |
| Systemd integration | Via systemd service | Via systemd service | Via Quadlet (.container files) |
| Stars on GitHub | 20,594 | 5,601 | 31,432 |
| OCI compliance | Full | Full | Full |
| Low-level runtime | runc, crun, others | runc, crun, Kata | runc, crun |
| Image build | No (external) | No (external) | Yes (via Buildah integration) |
| Best for | Kubernetes clusters, Docker backend | Minimal Kubernetes nodes | Development, rootless production, systemd-native workflows |
When to Use Each Runtime
Choose containerd if:
- You’re running a Kubernetes cluster and want the battle-tested default
- You need multi-tenant isolation via containerd namespaces
- You want broad ecosystem compatibility (Docker uses it internally)
- You need NRI plugins for custom resource management hooks
- You’re building a platform on top of containers and need a stable gRPC API
containerd is the safe, proven choice. It’s what Kubernetes used by default after Docker was deprecated as a runtime in v1.24. Most managed Kubernetes services run containerd underneath.
Choose CRI-O if:
- Your only workload is Kubernetes — no other container use cases
- You want the smallest possible runtime footprint on each node
- You’re running OpenShift or a Red Hat-based distribution
- Security audit requirements demand the minimal attack surface
- You want to use crun as the default low-level runtime for faster startup
CRI-O is the specialist. It does one thing extremely well and nothing else. If you don’t need Docker API compatibility, general-purpose container management, or daemonless operation, CRI-O is the leanest option.
Choose Podman if:
- You want a Docker replacement on your workstation or server
- You need rootless containers for security compliance
- You prefer systemd-native container management via Quadlet
- You want to run container pods without Kubernetes
- You’re a developer who wants the Docker CLI experience without the Docker daemon
Podman is the generalist for non-Kubernetes workloads. It covers the same ground that Docker does — image management, container lifecycle, Compose compatibility — but with a more secure, daemonless architecture.
Security Comparison
Rootless Execution
| Runtime | Rootless Mode | Implementation |
|---|---|---|
| containerd | Not supported natively | Requires root for the daemon; containers run as the daemon user |
| CRI-O | Via crun user namespaces | Kubernetes manages user namespace mapping through the CRI spec |
| Podman | Native, default mode | Uses Linux user namespaces (unshare --map-root-user) for full rootless isolation |
Podman’s rootless support is the most mature. It uses subuid/subgid mappings to give each container its own uid range:
| |
Image Signing and Verification
All three runtimes support image verification, but the mechanisms differ:
| |
Performance Benchmarks
Based on publicly available benchmarks and community testing:
| Metric | containerd | CRI-O + crun | Podman + crun |
|---|---|---|---|
| Container start time | ~200ms | ~150ms | ~180ms |
| Memory overhead (daemon) | ~50MB | ~30MB | 0MB (daemonless) |
| Image pull throughput | Fast | Fast | Fast |
| Concurrent containers | 10,000+ | 10,000+ | Limited by user session |
CRI-O with crun tends to have the fastest container startup because it has the least abstraction layer between the kubelet and the OCI runtime. Podman’s daemonless model means zero baseline memory overhead — but each session has its own overhead, which can add up on systems running many isolated containers.
Migration Paths
From Docker to containerd
If you’re currently using Docker Engine and want to understand what’s underneath:
| |
For a drop-in Docker CLI replacement that uses containerd underneath, consider nerdctl:
| |
From Docker to Podman
The migration is nearly seamless:
| |
Frequently Asked Questions
Can I use Podman with Kubernetes?
Yes, but not as a direct kubelet runtime. Podman supports podman play kube to deploy Kubernetes YAML files directly:
| |
For running Podman containers inside Kubernetes pods, you can use Podman as a runtime via the CRI-O-compatible podman-remote socket, but this is not a common production pattern. For Kubernetes clusters, containerd or CRI-O are the recommended choices.
Is containerd a replacement for Docker?
Not directly. containerd is the runtime that Docker uses internally. If you remove Docker, you lose the Docker CLI, Compose support, and image building. To replace Docker with containerd, you need additional tools like nerdctl for the CLI and BuildKit for image building. Podman is a more direct Docker replacement since it includes the CLI and build capabilities.
Which runtime is most secure for production?
For rootless operation, Podman is the clear winner — it supports rootless containers natively with minimal configuration. For Kubernetes workloads, CRI-O has the smallest attack surface because it implements only the CRI specification and nothing else. For general-purpose servers, containerd with proper namespace isolation and network policies provides enterprise-grade security.
Can I switch runtimes on an existing Kubernetes cluster?
Yes, but it requires draining nodes and changing the kubelet configuration. The process involves:
| |
Always test in a non-production environment first. Not all container images behave identically across runtimes.
Does CRI-O support Docker Compose files?
No. CRI-O is purpose-built for Kubernetes and does not support Docker Compose or any standalone container management. If you need Compose support alongside Kubernetes, pair CRI-O (for Kubernetes) with Podman or containerd+nerdctl (for standalone workloads) on the same node.
What is the difference between runc and crun?
Both are OCI-compliant low-level container runtimes. runc is the reference implementation written in Go, used by default in Docker and containerd. crun is a faster alternative written in C, with lower memory usage and faster startup times. CRI-O supports both, and Podman can use either. Switching to crun typically reduces container startup time by 20-30%.
How do I monitor container runtime performance?
For containerd, use the built-in metrics endpoint:
| |
For CRI-O, use crictl stats:
| |
For Podman, use podman stats:
| |
Summary
| Criteria | containerd | CRI-O | Podman |
|---|---|---|---|
| Best for | Kubernetes + general purpose | Kubernetes-only | Development + rootless production |
| Complexity | Medium | Low | Low-Medium |
| Security | Good (daemonized) | Excellent (minimal) | Excellent (rootless) |
| Kubernetes | Native (default) | Native (purpose-built) | Limited (play kube) |
| Standalone containers | Via nerdctl | Not supported | Native |
| Learning curve | Moderate | Low | Low (Docker-compatible CLI) |
For a complete container ecosystem, consider combining these with our guides on container build tools for image creation, container registries for image storage, and Kubernetes distributions for orchestration.