A container runtime is the software that creates, runs and manages containers on a host: it unpacks container images into a filesystem, configures Linux kernel isolation features such as namespaces and cgroups, starts the container process and supervises it until it stops. In the field of containers, the runtime is the layer between higher-level tools such as Docker, Podman or Kubernetes and the operating system kernel that actually enforces isolation.
The term covers two distinct layers. A high-level runtime such as containerd or CRI-O manages images, storage and the container lifecycle, while a low-level runtime such as runc or crun performs the final step of spawning the isolated process according to the Open Container Initiative (OCI) runtime specification.
How a container runtime starts a container
Starting a container is a chain of steps divided between the two runtime layers. The high-level runtime prepares everything the container needs, then hands a bundle, consisting of a root filesystem and a config.json file, to the low-level runtime.
- Pull the image from a container registry and verify its layers by digest.
- Unpack and stack the layers with a snapshotter, usually overlayfs, adding a writable top layer.
- Generate the OCI configuration with the command, environment, mounts, limits and security settings.
- Create namespaces for process IDs, networking, mounts, hostname, IPC and optionally users.
- Apply cgroup limits for CPU, memory, I/O and process count.
- Apply security filters such as seccomp profiles, dropped capabilities and AppArmor or SELinux labels.
- Execute the entry process, after which a small shim process keeps supervising it.
The shim lets the high-level runtime restart or upgrade without killing running containers. It holds the container’s standard streams open, collects its exit status and reports events back to containerd or CRI-O.
High-level and low-level container runtimes
The split between layers is the key to understanding the runtime landscape. Low-level runtimes are small and focused on process isolation, while high-level runtimes handle images, storage, networking hooks and APIs.
| Runtime | Layer | Main characteristics |
|---|---|---|
| runc | Low-level | OCI reference implementation, written in Go |
| crun | Low-level | Written in C, faster startup and lower memory |
| containerd | High-level | Graduated CNCF project, used by Docker and Kubernetes |
| CRI-O | High-level | Built only for Kubernetes through the CRI |
| gVisor (runsc) | Low-level, sandboxed | User-space kernel intercepts system calls |
| Kata Containers | Low-level, VM-based | Each pod runs in a lightweight virtual machine |
Docker itself is not a runtime in the strict sense. The Docker Engine is a higher-level platform that delegates container execution to containerd, which in turn calls runc. Podman works without a long-running daemon and calls runc or crun directly through its conmon monitor process.
Container runtimes in Kubernetes
Kubernetes talks to runtimes through the Container Runtime Interface (CRI), a gRPC API that the kubelet uses to create pod sandboxes, pull images and manage containers. Any runtime that implements the CRI can run Kubernetes workloads.
Kubernetes removed its built-in Docker integration, known as dockershim, in version 1.24. Clusters now use containerd or CRI-O directly. Images built with Docker remain fully compatible, because all of these tools follow the same OCI image format.
| Interface | Defines | Used between |
|---|---|---|
| OCI runtime specification | Bundle format and container lifecycle | High-level and low-level runtimes |
| OCI image specification | Image layers, manifests and configuration | Builders, registries and runtimes |
| Container Runtime Interface | gRPC API for pods and containers | Kubelet and high-level runtime |
| Container Network Interface | Plugin API for container networking | Runtime and network plugins |
A RuntimeClass object lets a cluster offer several low-level runtimes side by side, for example runc for ordinary workloads and Kata Containers or gVisor for untrusted code, chosen per pod.
Isolation strength and runtime choice
Standard runtimes share the host kernel with every container, so a kernel vulnerability can allow a container escape. Sandboxed and VM-based runtimes add a second boundary at the cost of performance and compatibility.
| Runtime type | Isolation boundary | Overhead | Best suited to |
|---|---|---|---|
| Namespace-based (runc, crun) | Shared kernel with namespaces | Minimal | Trusted internal workloads |
| Rootless containers | User namespaces, no root on host | Low | Developer machines, shared hosts |
| User-space kernel (gVisor) | Syscalls handled in a sandbox | Moderate, I/O heavy | Untrusted or multi-tenant code |
| MicroVM (Kata, Firecracker) | Separate guest kernel | Higher startup and memory | Strong tenant separation |
For most internal applications, runc or crun with a restrictive seccomp profile, dropped capabilities and a non-root user provides a good balance. Multi-tenant platforms that run customer code usually add a sandboxed or VM-based runtime.
Common runtime problems and mistakes
Many container failures that appear to be application bugs are really runtime configuration issues. The most frequent ones include:
- Out-of-memory kills when the cgroup memory limit is lower than the application’s real peak.
- CPU throttling under tight CFS quotas, causing latency spikes even when the host is idle.
- cgroup driver mismatch between the kubelet and the runtime, preventing pods from starting.
- Running containers as root with full capabilities, widening the impact of any compromise.
- Filling the snapshot storage with unused images and layers, which blocks new containers.
- Mixing debugging tools: the docker command cannot see containers managed directly by containerd; crictl or nerdctl are needed.
Regular image garbage collection, explicit resource requests and limits and a consistent cgroup v2 setup across nodes prevent most of these problems.
Understanding it simply
A container is like a sealed lunchbox holding an application and everything it needs. Someone still has to open the box, set up a private space on the computer and start the application inside it. The container runtime is that worker.
It takes the packaged application, gives it its own walls, a fair share of memory and processor time, and makes sure it cannot wander into other spaces. Tools that people use every day, such as Docker or Kubernetes, give the instructions, but the runtime does the actual work.
Applied in practice
An online education company runs a Kubernetes cluster where students submit code that is compiled and tested automatically. Running untrusted student code with the default runc runtime worries the security team, because all containers share the node’s kernel.
The platform team installs gVisor alongside containerd and defines a RuntimeClass for it. Grading pods use the sandboxed runtime with a 512 MiB memory limit and a two-minute timeout, while the company’s own web services keep using runc for full performance. Grading jobs run about 10 to 20 percent slower, which is acceptable, and a later kernel vulnerability affecting containers does not expose the grading nodes, because student code never calls the host kernel directly.