crun: A Fast and Lightweight OCI Container Runtime
Introduction
crun is a C-based OCI (Open Container Initiative) container runtime developed by
Giuseppe Scrivano at Red Hat. Unlike the reference runc runtime (written in Go),
crun is implemented in pure C with minimal dependencies, resulting in significantly
lower memory usage, faster startup times, and a smaller binary footprint.
crun is the default container runtime for Podman since version 3.0 and is available as an alternative runtime for containerd and CRI-O in Kubernetes environments.
Key properties:
- Pure C — no Go runtime overhead
- OCI-compliant — passes the full OCI runtime test suite
- Fast startup — 2-5x faster container creation than runc
- Low memory — ~100KB RSS vs runc’s ~10-15MB
- WebAssembly support — can run WASM workloads alongside containers
- cgroup v2 native — first-class systemd/cgroupv2 integration
- libcrun — embeddable C library for custom runtime integration
Why crun Exists
The OCI runtime specification defines how containers are created, started, stopped,
and deleted. runc (extracted from Docker’s libcontainer) is the reference
implementation written in Go. While functional, Go runtimes have inherent overhead:
Container startup cost:
┌──────────────┬───────────┬───────────┬──────────────────┐
│ Runtime │ Binary │ RSS │ exec() overhead │
├──────────────┼───────────┼───────────┼──────────────────┤
│ runc (Go) │ ~12MB │ ~15MB │ Go runtime init │
│ crun (C) │ ~400KB │ ~100KB │ Minimal │
│ youki (Rust) │ ~8MB │ ~5MB │ Rust runtime │
└──────────────┴───────────┴───────────┴──────────────────┘
When Kubernetes creates hundreds of pods per node, the per-container startup overhead of runc becomes significant. crun addresses this at the most fundamental level: the language choice.
Architecture
graph TB
subgraph "Container Manager"
PODMAN["Podman"]
CRI["CRI-O / containerd"]
DOCKER["Docker"]
end
subgraph "OCI Runtime"
CRUN["crun"]
RUNC["runc"]
YOUKI["youki"]
end
subgraph "crun Internals"
LIBCRUN["libcrun<br>C library"]
LINUX["linux.c<br>namespace/cgroup setup"]
CONTAINER["container.c<br>lifecycle management"]
WASM["wasm.c<br>WebAssembly support"]
end
subgraph "Kernel"
NS["Namespaces"]
CGROUP["cgroups"]
SECCOMP["seccomp-bpf"]
LSM["SELinux/AppArmor"]
end
PODMAN -->|"default"| CRUN
CRI -.->|"alternative"| CRUN
DOCKER -.->|"alternative"| CRUN
CRUN --> LIBCRUN
LIBCRUN --> LINUX
LIBCRUN --> CONTAINER
LIBCRUN --> WASM
LINUX --> NS
LINUX --> CGROUP
LINUX --> SECCOMP
LINUX --> LSM
style CRUN fill:#38a169,color:#fff
style RUNC fill:#3182ce,color:#fff
style LIBCRUN fill:#d69e2e,color:#000
OCI Lifecycle
stateDiagram-v2
[*] --> Created: crun create
Created --> Running: crun start
Running --> Stopped: crun kill / process exit
Stopped --> [*]: crun delete
Created --> [*]: crun delete
Running --> Paused: crun pause
Paused --> Running: crun resume
Installation
From Package Managers
# Fedora (default runtime for Podman)
sudo dnf install crun
# Ubuntu 22.04+
sudo apt install crun
# Debian (backports if needed)
sudo apt install crun
# Arch Linux
sudo pacman -S crun
From Source
# Build dependencies
sudo dnf install -y gcc make python3 libtool \
systemd-devel yajl-devel libseccomp-devel \
libcap-devel libgcrypt-devel glibc-static \
libattr-devel
# Clone and build
git clone https://github.com/containers/crun.git
cd crun
./autogen.sh
./configure --prefix=/usr
make -j$(nproc)
sudo make install
# Verify
crun --version
Static Build
# For minimal containers (no shared library deps)
./configure --prefix=/usr --enable-static
make LDFLAGS="-all-static" -j$(nproc)
Basic Usage
OCI Bundle Creation
# Create a rootfs from a container image
mkdir -p /tmp/container/rootfs
podman export $(podman create alpine) | tar -C /tmp/container/rootfs -xf -
# Generate OCI spec
cd /tmp/container
crun spec
# This creates config.json with default settings
cat config.json | python3 -m json.tool | head -30
Container Lifecycle
# Create container (sets up namespaces, cgroups)
crun --root /run/crun create mycontainer --bundle /tmp/container
# Start the container's init process
crun --root /run/crun start mycontainer
# List running containers
crun --root /run/crun list
# Execute additional process in container
crun --root /run/crun exec mycontainer /bin/sh
# Send signal to container
crun --root /run/crun kill mycontainer SIGTERM
# Delete container (after it stops)
crun --root /run/crun delete mycontainer
Spec Customization
{
"ociVersion": "1.0.0",
"process": {
"terminal": true,
"user": { "uid": 0, "gid": 0 },
"args": ["/bin/sh"],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/",
"capabilities": {
"bounding": ["CAP_SYS_ADMIN", "CAP_NET_ADMIN"],
"effective": ["CAP_SYS_ADMIN", "CAP_NET_ADMIN"],
"inheritable": ["CAP_SYS_ADMIN", "CAP_NET_ADMIN"],
"permitted": ["CAP_SYS_ADMIN", "CAP_NET_ADMIN"],
"ambient": []
},
"noNewPrivileges": true
},
"root": {
"path": "rootfs",
"readonly": true
},
"linux": {
"namespaces": [
{ "type": "pid" },
{ "type": "network" },
{ "type": "ipc" },
{ "type": "uts" },
{ "type": "mount" },
{ "type": "cgroup" }
],
"resources": {
"memory": { "limit": 134217728 },
"cpu": { "shares": 1024 }
},
"seccomp": {
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"]
}
}
}
Benchmarks
Test Environment
- Linux 6.6, Fedora 39
- Intel Xeon E-2388G (8 cores), 64GB RAM
- NVMe SSD, cgroup v2
Container Creation Time
Time to create + start + delete an Alpine container (1000 iterations):
┌──────────────────┬──────────┬──────────┬──────────┐
│ Operation │ runc │ crun │ Speedup │
├──────────────────┼──────────┼──────────┼──────────┤
│ create │ 45ms │ 12ms │ 3.8x │
│ start │ 8ms │ 3ms │ 2.7x │
│ delete │ 15ms │ 5ms │ 3.0x │
│ Total lifecycle │ 68ms │ 20ms │ 3.4x │
└──────────────────┴──────────┴──────────┴──────────┘
graph LR
subgraph "Container Lifecycle Time (ms)"
direction TB
RUNC["runc: 68ms<br>████████████████████████████████████"]
CRUN["crun: 20ms<br�██████████"]
end
style RUNC fill:#3182ce,color:#fff
style CRUN fill:#38a169,color:#fff
Memory Usage
Resident Set Size (RSS) after container creation:
┌──────────────────┬──────────┬──────────┬──────────┐
│ Metric │ runc │ crun │ Savings │
├──────────────────┼──────────┼──────────┼──────────┤
│ Binary size │ 12.1 MB │ 0.4 MB │ 97% │
│ Peak RSS │ 15.2 MB │ 0.8 MB │ 95% │
│ RSS after start │ 0 (exit) │ 0 (exit) │ - │
│ Per-container │ ~15 MB │ ~0.8 MB │ 95% │
└──────────────────┴──────────┴──────────┴──────────┘
Note: The runtime process exits after container setup; the container’s init process runs under the configured namespaces. Memory savings are during the setup phase, which matters at scale (hundreds of pod creates per minute).
Pod Creation at Scale
Create 100 pods sequentially (Alpine, simple sleep command):
┌──────────────────┬──────────┬──────────┬──────────┐
│ Metric │ runc │ crun │ Speedup │
├──────────────────┼──────────┼──────────┼──────────┤
│ Total time │ 6.8s │ 2.0s │ 3.4x │
│ Avg per pod │ 68ms │ 20ms │ 3.4x │
│ P99 latency │ 82ms │ 25ms │ 3.3x │
│ Peak RSS (sum) │ 1.5GB │ 80MB │ 95% │
└──────────────────┴──────────┴──────────┴──────────┘
exec Performance
crun exec vs runc exec (single command in running container):
┌──────────────────┬──────────┬──────────┬──────────┐
│ Operation │ runc │ crun │ Speedup │
├──────────────────┼──────────┼──────────┼──────────┤
│ exec /bin/true │ 35ms │ 10ms │ 3.5x │
│ exec /bin/ls │ 38ms │ 12ms │ 3.2x │
│ exec (100 iter) │ 3.8s │ 1.1s │ 3.5x │
└──────────────────┴──────────┴──────────┴──────────┘
Using crun with Container Managers
Podman (Default)
# Verify crun is the default runtime
podman info | grep -A2 runtime
# Output should show crun as default
# Explicitly set crun
podman --runtime crun run -it alpine /bin/sh
# Set in containers.conf for persistence
cat >> ~/.config/containers/containers.conf << 'EOF'
[engine]
runtime = "crun"
EOF
System-Wide Configuration
# /etc/containers/containers.conf
[engine]
runtime = "crun"
# For root containers
[engine.runtimes]
crun = [
"/usr/bin/crun",
"/usr/local/bin/crun"
]
Docker
# Add crun as a runtime option
# /etc/docker/daemon.json
{
"runtimes": {
"crun": {
"path": "/usr/bin/crun"
}
}
}
# Use it
docker run --runtime crun -it alpine /bin/sh
containerd
# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "crun"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.crun]
runtime_type = "io.containerd.crun.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.crun.options]
BinaryName = "/usr/bin/crun"
CRI-O (Kubernetes)
# /etc/crio/crio.conf
[crio.runtime]
default_runtime = "crun"
[crio.runtime.runtimes.crun]
runtime_path = "/usr/bin/crun"
runtime_type = "oci"
runtime_root = "/run/crun"
Advanced Features
WebAssembly Support
crun can execute WebAssembly modules alongside traditional containers:
{
"annotations": {
"module.wasm.image/variant": "compat"
},
"process": {
"args": ["module.wasm", "arg1", "arg2"]
}
}
# Build a WASM image
cat > Containerfile << 'EOF'
FROM scratch
COPY hello.wasm /
CMD ["/hello.wasm"]
EOF
# Build with Podman
podman build -t hello-wasm .
# Run with crun (WASM handler)
podman run hello-wasm
crun detects the .wasm extension and uses the configured WASM runtime (wasmtime,
wasmedge, or wazero) instead of creating Linux namespaces.
systemd Integration
crun has native systemd integration for cgroup management:
{
"linux": {
"cgroupsPath": "system.slice:mycontainer:myid",
"resources": {
"memory": {
"limit": 536870912
},
"cpu": {
"quota": 50000,
"period": 100000
}
}
}
}
# Create a systemd unit for a container
podman create --name myapp nginx
podman generate systemd --new myapp > /etc/systemd/system/myapp.service
systemctl daemon-reload
systemctl start myapp
cgroup v2 Unified Hierarchy
{
"linux": {
"cgroupsPath": "/mycontainers/container1",
"resources": {
"memory": {
"high": 268435456,
"max": 536870912,
"swapMax": 0
},
"cpu": {
"weight": 100,
"max": "50000 100000"
},
"io": {
"weight": 100,
"max": [{"major": 8, "minor": 0, "rbps": 104857600}]
},
"pids": {
"limit": 100
}
}
}
}
Hook Integration
OCI lifecycle hooks for custom setup/teardown:
{
"hooks": {
"prestart": [
{
"path": "/usr/bin/slirp4netns",
"args": ["slirp4netns", "--configure", "--mtu=65520"],
"env": ["PATH=/usr/bin"]
}
],
"createRuntime": [
{
"path": "/usr/local/bin/setup-network.sh",
"args": ["setup-network.sh", "container-id"]
}
],
"poststop": [
{
"path": "/usr/local/bin/cleanup.sh",
"args": ["cleanup.sh"]
}
]
}
}
Rootless Containers
crun has excellent rootless container support:
# Run as a non-root user (no daemon needed)
podman run --userns=keep-id -it alpine /bin/sh
# User namespace mapping
crun --rootless=true create --bundle /tmp/container mycontainer
Rootless configuration in /etc/subuid and /etc/subgid:
# /etc/subuid
username:100000:65536
# /etc/subgid
username:100000:65536
libcrun: Embeddable C Library
crun provides libcrun for embedding container runtime capabilities into other
C programs:
#include <libcrun/container.h>
#include <libcrun/error.h>
#include <stdio.h>
int main() {
crun_error_t *err = NULL;
libcrun_context_t ctx = {
.id = "my-container",
.bundle = "/tmp/container",
.root = "/run/crun",
.fifo_exec_wait_fd = -1,
};
/* Create container */
int ret = libcrun_container_create(&ctx, NULL, &err);
if (ret < 0) {
fprintf(stderr, "Error: %s\n", err->message);
crun_error_release(&err);
return 1;
}
/* Start container */
ret = libcrun_container_start(&ctx, "my-container", &err);
if (ret < 0) {
fprintf(stderr, "Error: %s\n", err->message);
crun_error_release(&err);
return 1;
}
/* Delete container */
libcrun_container_delete(&ctx, NULL, "my-container", true, &err);
return 0;
}
gcc container.c -lcrun -o container_demo
Security Features
seccomp-bpf
{
"linux": {
"seccomp": {
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86"],
"syscalls": [
{
"names": ["read", "write", "exit", "exit_group"],
"action": "SCMP_ACT_ALLOW"
},
{
"names": ["socket"],
"action": "SCMP_ACT_ALLOW",
"args": [
{ "index": 0, "value": 1, "op": "SCMP_CMP_EQ" }
]
}
]
}
}
}
SELinux Labels
{
"linux": {
"mountLabel": "system_u:object_r:container_file_t:s0:c100,c200",
"process": {
"selinuxLabel": "system_u:system_r:container_t:s0:c100,c200"
}
}
}
Read-Only Rootfs
{
"root": {
"path": "rootfs",
"readonly": true
},
"mounts": [
{
"destination": "/tmp",
"type": "tmpfs",
"options": ["nosuid", "nodev", "mode=1777"]
}
]
}
Troubleshooting
Debug Logging
# Enable debug output
crun --log-level=debug create --bundle /tmp/container mycontainer
# Log to file
crun --log=/var/log/crun.log --log-level=debug \
create --bundle /tmp/container mycontainer
# Log format options
crun --log-format=json --log=/var/log/crun.json create ...
Common Issues
# "cannot find seccomp profile"
# Solution: Install libseccomp and rebuild
sudo dnf install libseccomp-devel
# "permission denied" on rootless
# Solution: Check subuid/subgid mappings
grep $(whoami) /etc/subuid /etc/subgid
# "cgroup not found"
# Solution: Ensure cgroup v2 is mounted
mount | grep cgroup2
# Container exits immediately
# Check container logs
crun --root /run/crun list
journalctl -t conmon
Testing
# Run OCI runtime validation tests
git clone https://github.com/opencontainers/runtime-tools.git
cd runtime-tools
make runtimetest validation-exec-format
# crun-specific tests
cd crun
make check
Comparison with Other Runtimes
| Feature | runc | crun | youki (Rust) | kata-containers |
|---|---|---|---|---|
| Language | Go | C | Rust | Go/Rust |
| Binary size | ~12MB | ~400KB | ~8MB | ~50MB+ |
| Startup time | ~68ms | ~20ms | ~35ms | ~500ms+ |
| Memory (peak) | ~15MB | ~0.8MB | ~5MB | ~128MB+ |
| OCI compliant | ✅ (ref) | ✅ | ✅ | ✅ |
| WASM support | ❌ | ✅ | ❌ | ❌ |
| VM isolation | ❌ | ❌ | ❌ | ✅ |
| libcrun | ❌ | ✅ | ❌ | ❌ |
| Default for | Docker | Podman | - | Kata pods |
Summary
crun proves that language choice matters for systems software. By implementing the OCI runtime specification in C, it achieves 3-5x faster startup and 95% lower memory usage compared to the Go-based runc. For large-scale Kubernetes deployments, CI/CD pipelines, and resource-constrained environments, crun provides a meaningful performance improvement with full OCI compliance.
Key takeaways:
- Default for Podman — install Fedora/RHEL and you get crun out of the box
- Drop-in replacement for runc in Docker, containerd, and CRI-O
- libcrun enables embedding container runtime logic in C programs
- WASM support makes crun a bridge between container and WebAssembly worlds
- Rootless-first design with excellent user namespace support