The “Runtime” Confusion
This is where terminology gets messy. Everyone calls different things a “runtime.”
The Problem
"Container Runtime" — but which one?
Docker? ← Sometimes called a runtime
containerd? ← Called a "high-level runtime"
runc? ← Called a "low-level runtime"
All three get called "runtime" in different contexts.
The Solution: Think in Layers
| Term | What It Actually Means | Examples |
|---|---|---|
| Container Engine | Full platform with CLI, image building, UX | Docker, Podman |
| High-Level Runtime | Manages container lifecycle, images, storage | containerd, CRI-O |
| Low-Level Runtime (OCI) | Spawns processes with namespaces/cgroups | runc, crun, youki, gVisor |
| Language Runtime | Executes application code | JVM, Node.js, .NET Core runtime |
Note: In the context of containers, we focus on container runtimes (engine, high-level, low-level). Language runtimes like JVM or Node.js run inside the container to execute your application code. You can ignore them when discussing container architecture, but it’s useful to know they exist so you don’t confuse “container runtime” with “language runtime.”
Low-Level Runtime: The Actual “Runtime”
A low-level runtime does exactly one thing:
Input: OCI bundle (config.json + root filesystem)
Output: Running Linux process with isolation
That's it. Nothing else.
It doesn’t know about:
- Images or registries
- Networking configuration
- Storage drivers
- Logging
runc is the reference implementation. But there are alternatives:
| Runtime | Written In | Key Feature |
|---|---|---|
| runc | Go | Reference OCI implementation |
| crun | C | Faster, lower memory footprint |
| youki | Rust | Memory-safe, modern |
| gVisor | Go | Sandboxed kernel for extra isolation |
| Kata Containers | Go | Each container gets a micro-VM |
High-Level Runtime: The Manager
A high-level runtime handles everything the low-level runtime doesn’t:
containerd's responsibilities:
├── Pull images from registries
├── Unpack image layers
├── Manage container lifecycle (create → start → stop → delete)
├── Handle storage (snapshots, overlay filesystem)
├── Set up networking
├── Stream logs
└── Call runc when it's time to actually spawn the process
containerd is the most common high-level runtime. CRI-O (from Red Hat) is the alternative, designed specifically for Kubernetes.
The Complete Picture
┌─────────────────────────────────────────────────────┐
│ CONTAINER ENGINE │
│ (Docker, Podman, nerdctl) │
│ │
│ • User-facing CLI │
│ • Image building (Dockerfile → image) │
│ • Developer experience │
│ • docker-compose / orchestration │
└────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ HIGH-LEVEL RUNTIME │
│ (containerd, CRI-O) │
│ │
│ • Image management (pull, store) │
│ • Container lifecycle │
│ • Storage (snapshots, overlayfs) │
│ • Networking setup │
│ • Calls low-level runtime │
└────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ LOW-LEVEL RUNTIME (OCI) │
│ (runc, crun, youki, gVisor, Kata) │
│ │
│ • Reads OCI bundle (config.json + rootfs) │
│ • Creates namespaces │
│ • Sets up cgroups │
│ • Spawns the actual process │
│ • Exits immediately │
└────────────────────┬────────────────────────────────┘
│
▼
┌─────────────┐
│ CONTAINER │
│ (process) │
└─────────────┘
Why This Matters
For Debugging
When something breaks, knowing the layers helps:
Container won't start?
├── Is the image corrupted? → High-level runtime issue
├── Can't create namespace? → Low-level runtime / kernel issue
├── Network not working? → High-level runtime / Docker networking
└── Process crashes immediately? → Application issue
For Understanding Kubernetes
Kubernetes talks to the high-level runtime via CRI (Container Runtime Interface):
kubelet → CRI API → containerd or CRI-O → runc
Kubernetes doesn’t care about Docker’s CLI or image building. It only needs lifecycle management.
Choosing a Low-Level Runtime
| Runtime | When to Use |
|---|---|
| runc | Default choice, battle-tested, reference implementation |
| crun | When you need faster startup or lower memory (OpenShift default) |
| youki | When you want memory safety guarantees (Rust) |
| gVisor | When you need extra security isolation (untrusted workloads) |
| Kata | When you need VM-level isolation with container UX |
For most use cases, runc is perfectly fine. The alternatives optimize for specific scenarios.
Single-Sentence Truth (Memorize This)
containerd manages containers; runc creates containers.
That one sentence resolves 90% of confusion.
Key Takeaways
- “Runtime” is overloaded - Always clarify: engine, high-level, or low-level
- Low-level = process spawner - Just creates the isolated process
- High-level = lifecycle manager - Handles images, storage, networking
- Container engine = user interface - CLI, UX, image building
- runc is the standard - Reference OCI implementation, used by default
- containerd is the standard manager - Used by Docker, Kubernetes, and clouds
The OCI Standard: Why This All Works
OCI (Open Container Initiative) - Linux Foundation project (2015) that standardizes three things:
- Runtime Spec - How to run containers (config.json + rootfs → process)
- Image Spec - How images are structured (layers, manifests, config)
- Distribution Spec - How registries work (push/pull, auth)
Why it matters: Docker donated runc as the reference implementation, ensuring all OCI runtimes are interchangeable.
# Same image works everywhere
docker build -t myapp .
podman run myapp # Works
nerdctl run myapp # Works
kubectl run myapp # Works
Without OCI: Vendor lock-in. With OCI: Build once, run anywhere.
Comments