Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

Featurerunccrunyouki (Rust)kata-containers
LanguageGoCRustGo/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 forDockerPodman-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:

  1. Default for Podman — install Fedora/RHEL and you get crun out of the box
  2. Drop-in replacement for runc in Docker, containerd, and CRI-O
  3. libcrun enables embedding container runtime logic in C programs
  4. WASM support makes crun a bridge between container and WebAssembly worlds
  5. Rootless-first design with excellent user namespace support