🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

What Is a Container Runtime? containerd, CRI-O, and Docker Explained

A clear explanation of what a container runtime is, the difference between high-level and low-level runtimes, and why Kubernetes moved away from Docker to containerd and CRI-O.

Shubham3 min read
Share:Tweet

When people first learn Kubernetes, they often assume Docker is running the containers inside the cluster. After all, you built your image with Docker. It makes sense that Docker would run it too.

But if you check a modern Kubernetes node, you will find Docker is not there. Instead you will see containerd or CRI-O. Let me explain what is actually going on.

What Is a Container Runtime?

A container runtime is the software responsible for actually running containers on a host. When Kubernetes decides it needs to start a pod, something has to handle the low-level work of pulling the image, setting up filesystem layers, creating Linux namespaces to isolate the process, setting up cgroups for resource limits, and starting the actual process.

That "something" is the container runtime.

High-Level vs Low-Level Runtimes

There are actually two layers:

High-level runtimes manage image pulling, image storage, and container lifecycle. They talk to Kubernetes via a standard API called the CRI (Container Runtime Interface). Examples: containerd, CRI-O.

Low-level runtimes do the actual Linux-level work: namespaces, cgroups, process execution. The standard here is the OCI (Open Container Initiative) Runtime Specification. Examples: runc, crun, gVisor, Kata Containers.

In a typical Kubernetes setup:

Kubernetes (kubelet)
  containerd (high-level runtime, via CRI)
    runc (low-level runtime, via OCI)
      your container process

Why Kubernetes Dropped Docker

Docker was originally designed as a developer tool — a complete platform for building, shipping, and running containers. Kubernetes only needed one small piece: the ability to run containers. But to use Docker, Kubernetes had to use it through a compatibility layer called dockershim.

This created real problems:

  • dockershim was maintained by the Kubernetes team, not Docker
  • Docker added overhead and complexity that Kubernetes did not need
  • Docker features like swarm and compose added unnecessary attack surface

In Kubernetes 1.24 (released May 2022), Docker support was removed entirely.

The good news: your Docker-built images still work perfectly. Docker images follow the OCI image spec, which containerd and CRI-O both support. Nothing about your workflow changed.

containerd

containerd is a high-level container runtime originally extracted from Docker itself. Docker actually uses containerd internally. Today it is a standalone CNCF graduated project used by AWS EKS, GKE, and Azure AKS as the default runtime.

On a Kubernetes node, use crictl instead of docker:

bash
# List running containers
crictl ps
 
# Pull an image
crictl pull nginx:latest
 
# Inspect a container
crictl inspect <container-id>

CRI-O

CRI-O is a lightweight container runtime built specifically for Kubernetes. It implements exactly the CRI and nothing more. No extra features, no CLI for developers, no image building. Just what Kubernetes needs.

It is the default runtime for Red Hat OpenShift and is popular in security-focused environments because its minimal footprint means a smaller attack surface.

Specialized Low-Level Runtimes

The OCI spec means low-level runtimes are pluggable:

runc — The default. Fast, battle-tested, runs containers as regular Linux processes.

gVisor — Runs containers inside a user-space kernel. Stronger isolation, some performance overhead. Used for multi-tenant workloads where you do not fully trust container code.

Kata Containers — Runs each container inside a lightweight VM. Maximum isolation, more overhead.

In Kubernetes, you select the runtime via RuntimeClass:

yaml
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc
---
apiVersion: v1
kind: Pod
spec:
  runtimeClassName: gvisor
  containers:
  - name: myapp
    image: myapp:latest

Quick Reference

RuntimeTypeUsed ByKey Feature
containerdHigh-levelEKS, GKE, AKSDefault, battle-tested
CRI-OHigh-levelOpenShiftMinimal, K8s-only
DockerHigh-levelDeveloper workstationsFull-featured dev tool
runcLow-levelMost clustersFast, standard
gVisorLow-levelSecurity workloadsKernel isolation
Kata ContainersLow-levelMaximum isolationVM-level isolation

For day-to-day work, the container runtime is invisible. The important thing: when you SSH into a Kubernetes node, reach for crictl, not docker.


Want to understand Kubernetes internals better? Read our Kubernetes architecture explained guide.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments