Skip to content

Sergei Ozeranskii

Kata Containers 4.0: the runtime rewritten in Rust — what changes inside

On 20 July 2026, Kata Containers 4.0 (opens in new tab) shipped. It’s a major release: the Kata runtime has been rewritten from Go to Rust — runtime-rs is now the default, and the old Go runtime is deprecated.

The first article explained why every CI job is worth running in its own micro-VM (a hardware boundary instead of a shared kernel). This one is about how it works on the host side and what 4.0 changes inside: the runtime, the storage model, the rootfs integrity guarantees.

A boundary note: everything below is a property of Kata the technology (per the official notes and docs), not of the tempus.build configuration.

What runtime-rs is, and why it isn’t “a port from Go”

On the host side, Kata is a Containerd Runtime V2 (Shim API): one long-lived shim process per pod instead of invoking the runtime for every operation. runtime: one process serves all containers of a single VM and talks to the container manager (containerd) over a socket. That contract didn’t change in 4.0 — the implementation did.

runtime-rs is built from separate crates with clear areas of responsibility (the src/runtime-rs/crates tree at tag 4.0.0):

runtime-rs layers: shim (containerd shim v2 entry point) → service (TaskService) → runtimes (VirtContainer) → resource (network, share_fs, rootfs, cgroups, cpu_mem); to the side — hypervisor with backends QEMU/Cloud Hypervisor/Dragonball and a dashed Firecracker (in the code, but not in the official 4.0 list), agent over ttRPC and persist for state recovery.
A layered architecture: resources and hypervisors sit behind common interfaces. Firecracker (dashed) is in the runtime-rs code but not in the official list of 4.0 hypervisors.
  • shim — the containerd shim v2 entry point: start / delete / run.
  • service — an extensible service framework; today it holds TaskService over the containerd shim protocol.
  • runtimes — the handlers: VirtContainer (default, VM), LinuxContainer and WasmContainer (experimental). They manage the sandbox/container lifecycle.
  • resource — a single resource-abstraction layer: sandbox-level (network, share_fs) and container-level (rootfs, volume, cgroups, cpu_mem).
  • hypervisor — the VMM abstraction: qemu, ch, dragonball, firecracker, remote backends behind a common interface; device handling lives in a separate module.
  • agent — the link to the guest kata-agent over A compact gRPC-style protocol for low-level IPC, without HTTP/2 — built for minimal overhead. (KataAgent).
  • persist — serializes the sandbox state to disk (state.json) to recover after a restart.

Because of this split, a new storage or network model is added in one layer (resource or the matching backend) and works across all VMMs at once — hence the “unified” 4.0 improvements below.

To keep the versions straight: in 3.x the default was the Go runtime, while runtime-rs shipped as experimental — its own README at tag 3.2.0 warned to “avoid using it in critical system” and noted that only the built-in Dragonball was implemented. Rust arrived in Kata before the runtime did: from v2 on, key components were moved to it (the kata-agent among them). 4.0 completes the multi-year transition: The Rust implementation of the Kata runtime (containerd shim v2); the default runtime since 4.0, replacing the Go one. becomes the default runtime, and the Go runtime is deprecated (no new features, only critical bug and CVE fixes until it’s removed).

Why Rust — and what it does for security

Kata’s official motivation from the release preview is three points: a smaller memory footprint, better security (“leveraging Rust’s memory safety to further harden the container sandbox”), and performance (lower latency, faster start). The project deliberately publishes no benchmark numbers — so we quote none either.

The runtime/shim is a host-side process that receives and parses input from an untrusted side: ttRPC messages from the agent inside the guest, device descriptions, VMM API responses. Rust’s memory safety (in the safe subset, via the borrow checker and no manual memory management) removes, at the language level, a whole class of bugs in that code: use-after-free, double-free, buffer overruns, data races. For a component that parses untrusted input, that shrinks the attack surface.

Don’t overstate the scope, though:

  • Rust doesn’t remove logic bugs: authorization, validation, and TOCTOU errors are still possible.
  • The VM boundary is held by the hypervisor, not the runtime. The VMM’s own memory safety depends on the choice: QEMU is written in C, Cloud Hypervisor and Dragonball in Rust. “The whole stack is now memory-safe” is wrong; the host-side runtime got safer, not the hardware emulation.
  • unsafe blocks remain in Rust (FFI, device access) — the language’s guarantees only cover the safe part.

Rust removes a class of memory-corruption bugs in the runtime and in host-side parsers — one more layer on top of the hardware boundary, not a replacement for it.

Storage: from DAX + virtio-fs to a block model

4.0 changes the model itself. In the Go era the guest VM image was mapped through Direct Access — the hypervisor maps the guest image straight into guest memory (zero-copy), saving both memory and boot time./NVDIMM (/dev/pmem*), and the container rootfs was passed through via A paravirtual shared filesystem: the virtiofsd daemon on the host exposes the container rootfs to the guest; host and guest see one file tree./9p. In 4.0 there’s a single block device model (internally BlockModern), shared by QEMU, Cloud Hypervisor and Dragonball: image layers are handed to the guest as block devices, bypassing virtio-fs/9p.

The new EROFS snapshotter for containerd works like this:

  1. OCI image layers are converted into Enhanced Read-Only File System — a compressed, immutable, block-aligned Linux kernel filesystem, well suited as an image carrier. blobs (read-only, compressed).
  2. Mounts are prepared: an ext4 RW layer, EROFS RO layers, and an overlay combining them.
  3. If there are several RO layers, runtime-rs generates a VMDK descriptor (the twoGbMaxExtentFlat format) that concatenates the layer files into one virtual block device — that’s the “VMDK” from the release notes.
  4. The devices are attached to the guest over virtio-blk; inside the VM the kata-agent mounts them and assembles the overlay into the container’s single rootfs.

Also in the 4.0 block model: reliable hot-plug/unplug of block devices with rollback on failure, thin-provisioning support (discard/unmap, where the hypervisor supports it), and an improved A paravirtual SCSI transport for guest block devices; improved in 4.0 as a container rootfs driver. as the rootfs driver.

There’s a second reason for the turn toward block devices — a verifiable rootfs.

Verified rootfs: dm-verity + EROFS

Now that the rootfs is a read-only block device, its integrity can be checked cryptographically. 4.0 adds an integrity-verified rootfs via A Linux device-mapper target: transparent integrity checking of a read-only block device via a Merkle hash tree with a trusted root hash; tampered data fails the read. (+ GPT/VMDK), and in the deployment configs dm-verity is on by default.

rootfs verification path: OCI layers → EROFS blob (read-only, compressed) → VMDK concatenation → virtio-blk into the guest → dm-verity checks hashes along a Merkle tree up to a trusted root hash; on a match the rootfs mounts read-only, on tampered data an I/O error.
A block-based RO rootfs is checked against a Merkle tree by dm-verity: any divergence from the trusted root hash is an I/O error, not a silent swap.

How dm-verity works (per the Linux kernel docs, not our reading):

  • A Merkle hash tree is built over fixed-size blocks: a leaf is the hash of a data block, an inner node the hash of its children.
  • The root hash is the trust anchor; everything else is verified against it — nothing is trusted without it.
  • The target is read-only, and the check is transparent on every disk access.
  • If a hash doesn’t verify up the tree to the root, the I/O operation fails — any data modification is detected (tamper-evidence).

EROFS complements this as the carrier: per the kernel docs it’s a “modern, efficient, and secure read-only” filesystem for immutable images — block-aligned, with compression (LZ4, MicroLZMA, DEFLATE, Zstandard). Immutable, block-based, compressed — a convenient carrier under dm-verity.

What this buys untrusted, multi-tenant CI: the guest won’t start on a tampered rootfs, and the host doesn’t modify the image unnoticed. For supply-chain, it’s the same class of guarantee an image signature gives — but at the runtime level and at execution time.

VMM: Dragonball built-in — and what about Firecracker

Officially supported hypervisors in the default (Rust) 4.0 runtime: QEMU, Cloud Hypervisor, Dragonball (x86_64, aarch64, s390x architectures).

Dragonball in 4.0 is positioned as a built-in Virtual Machine Monitor (hypervisor) — the process that creates and runs the VM. In Kata: QEMU, Cloud Hypervisor, Firecracker or Dragonball.: the runtime-rs README calls it “deeply integrated into shim lifecycle, eliminating IPC overhead”, and the community describes it as a VMM written in Rust (from Ant Group) that runs in the same process as the shim. What “in-process” means: no separate VMM process → no IPC boundary between runtime and hypervisor → less overhead and one process boundary instead of two.

Firecracker is trickier — the sources disagree. The runtime-rs 4.0 codebase has both a module (hypervisor/src/firecracker/) and a config (configuration-rs-fc.toml.in), and the README mentions it as an external hypervisor. But in the release’s “Supported Hypervisors” section Firecracker isn’t listed — only QEMU, Cloud Hypervisor and Dragonball are. So it’s in the code, but not in the official list of 4.0 hypervisors; its maturity level in 4.0 isn’t confirmed by the primary source. (A difference from the first article, where Firecracker was among the VMMs for 3.x.)

Memory, network, VFIO, overhead

  • Memory. Hotplug via A paravirtual mechanism to add memory to a running VM in blocks — finer-grained than a classic DIMM hotplug. (memory is added to a running VM in blocks, finer-grained than a classic DIMM hotplug); better memory management on QEMU; less overhead on IBM Secure Execution.
  • Network. Multi-queue is wired through all VMMs (QEMU/CH/Dragonball); improved hot-plug of network interfaces on QEMU (including under SR-IOV); network devices can sit in the host network namespace with QEMU.
  • VFIO / passthrough. Support for cold-plug of VFIO devices (the device is assigned before the VM starts — simpler and more reliable than hot-plug, but static, with no runtime changes); s390x VFIO-AP for IBM Z crypto/accelerators; for a standalone container — automatic CDI-based cold-plug.
  • Overhead accounting. Static sandbox sizing now accounts for runtime overhead more precisely. This is about Kubernetes Pod Overhead: RuntimeClass.overhead is added by the scheduler to a pod’s requests during bin-packing, and if the runtime reserves resources differently from the declared overhead, scheduling drifts. 4.0 brings the actual sizing in line with the accounted overhead.

What it means for a migration

  • The Go runtime is deprecated: only critical bug and CVE fixes, no new features. (The release preview’s forecast is removal “no earlier than 5.0.0”, but that’s a forecast, not a commitment.)
  • For Kubernetes, kata-deploy sets RuntimeClass = runtime-rs — the recommended path.
  • runtime-rs has a separate config tree (the -runtime-rs suffix), including variants for CoCo/TDX/SNP/SE.
  • The EROFS path needs pre-validation (kata-deploy checks prerequisites), dm-verity is on by default in the deployment configs, and the EROFS snapshotter is wired in via Helm.
  • Toolchain: Rust 1.95, containerd shim 0.11, ttrpc 0.9.
  • The release warns of “minor differences in configuration and behavior”; there’s no itemized list — when migrating, check docs/how-to and Limitations.md at tag 4.0.0.

How this maps to our approach

We don’t disclose our component versions — we keep the isolation stack at the current security floor and update it when guest→host class fixes land. But the direction of 4.0 lines up with our principles: a memory-safe host-side runtime (a smaller vulnerability class in the code that parses untrusted input) and a cryptographically verifiable rootfs (integrity and supply-chain at execution time) — both reinforce the same hardware boundary between jobs that the first article was about.

How the rest of our isolation layers are built, and how to verify a runner image yourself — on the Security & isolation page.

← back to the blog