CRI-O is a container runtime whose only job is to run containers for the Kubernetes kubelet. It implements the Container Runtime Interface (CRI), pulls OCI images, starts containers through an OCI runtime such as runc, crun or Kata Containers, and reports their state back to Kubernetes. It addresses the need for a small, predictable runtime that follows Kubernetes releases closely and carries no features beyond what a node requires. CRI-O was started by Red Hat in the Kubernetes incubator in 2016, joined the CNCF in April 2019 and graduated on 19 July 2023.
CNCF daily · Sprint 2, day 5 · Container Runtime
At a glance
| Category | Runtime › Container Runtime |
| CNCF status | Graduated. Accepted April 8, 2019; graduated July 19, 2023 |
| Written in | Go |
| License | Apache 2.0 |
| Scope | Kubernetes CRI only; no general-purpose container CLI or build tooling |
| Versioning | Each CRI-O minor version matches a Kubernetes minor version (for example CRI-O 1.31 with Kubernetes 1.31) |
| OCI runtimes | runc, crun and Kata Containers, selected per pod through runtime classes |
Architecture
flowchart TB
KUBELET[kubelet] ==>|CRI gRPC| CRIO[CRI-O daemon]
CRIO ==>|pull images| IMG[containers image<br/>library]
IMG ==>|fetch layers| REG[Container registry]
IMG ==>|unpack| STORE[containers storage<br/>layers on disk]
CRIO ==>|create sandbox| NET[CNI plugins<br/>pod network]
CRIO ==>|spawn per container| MON[conmon<br/>monitor process]
MON ==>|OCI runtime spec| RT[runc or crun<br/>or Kata]
RT ==>|namespaces and cgroups| CTR[Containers in the pod]
MON -.->|exit status and logs| CRIO
CRIO -.->|metrics and traces| OBS[Prometheus and OpenTelemetry]
classDef actor fill:#eceff1,stroke:#455a64,stroke-width:1.5px,color:#1d1c1a
classDef core fill:#0f766e,stroke:#064e4a,stroke-width:2px,color:#ffffff
classDef ctrl fill:#ede7f6,stroke:#5e35b1,stroke-width:1.5px,color:#1d1c1a
classDef data fill:#e3f2fd,stroke:#1565c0,stroke-width:1.5px,color:#1d1c1a
classDef store fill:#fff3e0,stroke:#e65100,stroke-width:1.5px,color:#1d1c1a
classDef obs fill:#e8f5e9,stroke:#2e7d32,stroke-width:1.5px,color:#1d1c1a
class KUBELET ctrl
class CRIO,MON,IMG core
class RT,CTR,NET data
class STORE,REG store
class OBS obs
Key: teal blocks are the project itself, blue are workloads and runtimes, purple are control and configuration, amber are storage, green are observability, grey are external actors. Thick arrows show the primary path, dashed arrows telemetry and status.
| Component | Responsibility |
|---|---|
| CRI-O daemon | Serves the CRI gRPC API to the kubelet, manages pod sandboxes and containers, and applies node-level configuration such as seccomp, SELinux and cgroup settings. |
| containers/image | Pulls and verifies OCI and Docker images from registries, including signature policy and registry mirror rules. |
| containers/storage | Stores image layers and container filesystems on the node, shared with tools such as Podman and Buildah. |
| conmon | A small monitor process started for each container. It keeps the container’s stdio and exit code alive even if the CRI-O daemon restarts. |
| OCI runtime | runc or crun creates the container from an OCI runtime spec; Kata Containers adds a lightweight virtual machine per pod. |
| CNI plugins | Set up pod networking through the standard Container Network Interface when the sandbox is created. |
Design principle. CRI-O does only what the kubelet asks for and relies on shared building blocks for everything else: the containers libraries for images and storage, conmon for supervision, and OCI runtimes for execution. Keeping the scope this narrow reduces the code that runs on every node and keeps its release cycle aligned with Kubernetes.
Production reference design
A typical deployment is a kubeadm, on-premises or OpenShift cluster where the node image is built with CRI-O as its runtime.
- Match versions. Install the CRI-O minor release that matches the cluster’s Kubernetes minor version, from the distribution’s CRI-O package repository.
- Install and start the service on each node (shown for a Debian-based node with the CRI-O repository already configured):
sudo apt-get install -y cri-o sudo systemctl enable --now crio sudo crictl --runtime-endpoint unix:///var/run/crio/crio.sock info - Point the kubelet at CRI-O by setting
--container-runtime-endpoint=unix:///var/run/crio/crio.sock, or passcriSocket: unix:///var/run/crio/crio.socktokubeadm initandkubeadm join. - Configure the runtime in a drop-in file so node upgrades do not overwrite it:
# /etc/crio/crio.conf.d/10-crio.conf [crio.runtime] default_runtime = "crun" cgroup_manager = "systemd" [crio.runtime.runtimes.crun] runtime_path = "/usr/bin/crun" [crio.runtime.runtimes.kata] runtime_path = "/usr/bin/kata-runtime" runtime_type = "vm" - Expose stronger isolation selectively with a RuntimeClass, so only chosen workloads pay the virtual machine cost:
Pods then setapiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: { name: kata } handler: kataruntimeClassName: kata.
Typical uses include OpenShift and other enterprise distributions, security-sensitive clusters that mix runc and Kata Containers, and teams that want the runtime version tied directly to the Kubernetes version they operate.
Operational considerations
- Version coupling. Upgrade CRI-O together with the kubelet on each node, keeping the same minor version. Running a CRI-O release far from the cluster version is unsupported and can break newer CRI features.
- Cgroup driver. The kubelet and CRI-O must use the same cgroup manager, normally
systemd. A mismatch is a common cause of pods that start and then fail. - Daemon restarts. Because conmon supervises each container, restarting the
crioservice does not stop running containers. Plan restarts as part of node maintenance anyway, and drain nodes for upgrades. - Image policy and mirrors. Registry mirrors, signature verification and pull policy are set in the containers configuration under
/etc/containers. Test them before enforcing, because a strict policy blocks pulls. - Debugging. Use
crictlto inspect pods, containers and images on a node, since the Docker CLI does not apply. Journal logs for thecriounit are the first place to look for sandbox failures.
Adoption
- The project’s
ADOPTERS.mdlists Red Hat OpenShift Container Platform, which uses CRI-O as its only supported runtime from OpenShift 4, and SUSE CaaS Platform, which made it the default in version 4. - It also lists Oracle Linux Cloud Native Environment, Lyft, Reddit, Adobe, Digital Science and HERE Technologies among production users, with Lyft reporting use since 2017 and Reddit since 2021 across its self-managed clusters.
- The CNCF graduation announcement in July 2023 reported more than ten public adopters and cited IBM Cloud Satellite and Adobe among them. The list is described as non-exhaustive.
Alternatives
| Solution | Model | Best suited to |
|---|---|---|
| containerd (CNCF Graduated) | General-purpose runtime used by most managed Kubernetes services | The common default; see the containerd brief |
| Docker Engine with cri-dockerd | Docker daemon behind a CRI adapter | Environments that still depend on Docker-specific tooling |
| Mirantis Container Runtime | Commercial Docker-based engine | Teams that need vendor support for a Docker lineage |
| Kata Containers or gVisor (as OCI runtimes) | Sandboxed runtimes used beneath CRI-O or containerd | Workloads that need stronger isolation than shared-kernel containers |
Strengths and limitations
| Strengths | Limitations |
|---|---|
| Minimal scope: only the CRI, with no extra daemon features | No general-purpose container CLI; node debugging uses crictl |
| Release versions track Kubernetes minors closely | Must be upgraded in step with Kubernetes, which adds node upgrade planning |
| Supports runc, crun and Kata Containers per pod through runtime classes | Smaller ecosystem and tutorial base than containerd |
| Default runtime on OpenShift, with enterprise vendor support | Most managed cloud Kubernetes services default to containerd, so CRI-O is mostly a self-managed choice |
| Shares the containers libraries with Podman and Buildah | Configuration is spread across crio and containers files, which needs clear ownership |
Recommendation
Adopt when you run OpenShift, or a self-managed cluster where you control the node image and want a runtime that follows Kubernetes releases and supports mixed runc and Kata workloads. If you run a managed cloud service, use the runtime the provider supports, which is usually containerd. Standardize on one runtime per cluster and build the CRI-O version into your node upgrade runbook.
References: CNCF project page · CRI-O website · CRI-O repository · CRI-O adopters · CNCF graduation announcement · CNCF Landscape