>_ DevTrendsja

言語

ホーム

言語

セクション

フロントエンド バックエンド モバイル DevOps AI / ML ゲーム開発 ブロックチェーン 組み込み セキュリティ
C

Why Rewrite a Container Runtime in C

Try running Docker or Podman with a memory limit of 512 kilobytes. The standard runtime runc will simply crash with an error at such a limit. It won't have enough memory even to set up basic process structures and read file descriptors.

The reason lies in the architecture. The vast majority of the modern container stack is written in Go. For top-level utilities this is great, but at the very lowest layer Go brings along its own runtime, garbage collector, and memory overhead. To set up Linux kernel namespaces before the main process starts, runc has to restart itself and resort to C workarounds.

The crun project from the Containers organization tackles this problem head-on. It's a lightweight implementation of the OCI (Open Container Initiative) specification, written entirely in pure C.

What switching to pure C gives you

The core concept is straightforward: strip away everything unnecessary from the critical path of container creation. When the runtime isn't burdened by extra layers, resource consumption and response speed improve dramatically.

Memory consumption and launching at the limits

The developers' benchmarks show a telling difference:

# Запуск через runc падает
$ podman --runtime /usr/bin/runc run --rm --memory 4M fedora echo it works
Error: container_linux.go:346: starting container process caused "process_linux.go:327: getting pipe fds for pid 13859 caused \"readlink /proc/13859/fd/0: no such file or directory\"": OCI runtime command not found error

# Запуск через crun отрабатывает без запинки
$ podman --runtime /usr/bin/crun run --rm --memory 512k fedora echo it works
it works

A container with crun starts reliably even with a strict memory limit of 512 KB, while runc stumbles even at 4 MB. For heavy cloud microservices, a megabyte difference might seem trivial, but on IoT devices, routers, and edge servers, this savings frees up precious memory for actual workloads.

Sequential startup speed

When you need to frequently launch short-lived tasks (for example, in serverless platforms or CI/CD runners), container initialization time becomes a bottleneck. In a test running 100 containers sequentially with the /bin/true command, the time difference is nearly twofold:

| Runtime | Execution time (100 containers) | Difference | | :--- | :--- | :--- | | crun | 0:01.69 | -49.4% | | runc | 0:03.34 | baseline |

The C implementation starts twice as fast thanks to no Go runtime initialization overhead and fewer unnecessary system calls.

Using as a library

Most runtimes only exist as standalone executables. If you need to launch an OCI container from your program, you typically have to fork() and invoke an external utility.

In crun, you can build a shared library libcrun. This opens up a direct C API for working with containers:

  • embedding isolated environment startup into your own daemons
  • no overhead from spawning separate processes
  • out-of-the-box support for Python and Lua bindings

Building and installation

The utility is available in most distributions through standard package managers, but building from source is also straightforward.

Building on Ubuntu requires basic header files:

$ sudo apt-get install -y make git gcc build-essential pkgconf libtool \
   libsystemd-dev libprotobuf-c-dev libcap-dev libseccomp-dev libjson-c-dev \
   go-md2man autoconf python3 automake

The compilation process itself is standard:

$ ./autogen.sh
$ ./configure
$ make
$ sudo make install

If you plan to use the library in your code, the configure flag will change to ./configure --enable-shared.

For reproducible environments and servers without extra dependencies, the project supports static builds via Nix Flakes:

$ nix --extra-experimental-features "nix-command flakes" build "path:.#crun-static-amd64"
$ ./result/bin/crun --version

The result is a compact static binary for x86_64 with glibc, ready to run on any target machine.

Who it's for

Switching to crun makes sense in several scenarios:

  1. Edge computing and IoT. When a device has only 256 MB or 512 MB of RAM soldered on, giving up megabytes to the runtime is unacceptable.
  2. Serverless and Functions-as-a-Service. In environments with frequent creation and destruction of isolated containers, a 50% startup speedup directly reduces cold start latency.
  3. Dense container packing. If hundreds of small workers run on a single host, the total RAM savings add up to gigabytes.

Swapping the runtime in Podman is as simple as a single flag --runtime /usr/bin/crun or changing a parameter in /etc/containers/containers.conf. No changes to manifests or images are needed.

関連プロジェクト