Skip to main content
k7d plugs into Kubernetes as a containerd runtime: pods with runtimeClassName: k7 run inside k7d microVMs instead of plain containers, and pods with runtimeClassName: k7-fc run the same guest under a stock Firecracker jail (see Backends). Four steps register the runtime on a k3s node.
The release installer does all four steps for you: sudo ./install.sh --with-k3s. The manual steps below are for understanding what it does, or for non-k3s containerd setups.
1

Install and start the daemon

The shim is a thin client: sandbox Create calls the daemon over /run/k7d/k7d.sock and fails loudly if it is not running.
The guest pair is required: the daemon is the only process that decides which guest a VM boots, and without K7D_KERNEL / K7D_INITRD it refuses to create a VM rather than guessing. LimitNOFILE matters as soon as you fork clusters — the 1024 soft default runs out fast.
2

Install the shim binary

containerd resolves the binary from the runtime type automatically (io.containerd.k7.v1 → containerd-shim-k7-v1).
3

Register the runtime in containerd

Add to /var/lib/rancher/k3s/agent/etc/containerd/config-v3.toml.tmpl:
Two things matter here:
  • pod_annotations forwards the k7d.katakate.org/* pod annotations (cluster-id, fork-vm-index, …) into the sandbox OCI spec so the shim can see them — required for cluster-mode pods deployed via kubectl.
  • Do not add BinaryName under [options] — the option value leaks into the cgroup path and breaks shim startup.
To also offer the Firecracker engine, add a second runtime that points the same shim at a config file selecting the backend (the k3s CRI does not stamp io.kubernetes.cri.runtime-handler, so the shim cannot tell the two handlers apart any other way):
4

Restart k3s and create the RuntimeClass

For k7-fc, add a RuntimeClass with the measured PodOverhead so kubelet accounts for the out-of-process VMM:
Verify with kubectl get runtimeclass k7 k7-fc — handlers k7 and k7-fc.

Using it

kubectl logs, kubectl exec (including -it with PTY, resize, Ctrl-C, detach), pod IPs, Services, DNS, ConfigMaps/Secrets, emptyDir/hostPath/PVC volumes, memory/CPU limits, init containers, natural exit / restartPolicy, and multi-vCPU guests all work — see the feature matrix for the full support table and what’s still to come (hostNetwork, NetworkPolicy, IPv6, cross-node fork). Swap in runtimeClassName: k7-fc for the Firecracker engine; everything above works there too except hostPath / virtiofs volumes and time warp, which fail Create with a named error. The pod’s securityContext is honoured inside the VM: seccomp profiles, maskedPaths / readonlyPaths, device cgroups, capabilities and noNewPrivileges are passed through to the guest runc unchanged — see Security.
CPU limits and vCPUs: the shim derives the guest’s vCPU count from the pod’s CPU limit (cpu: "2" boots a 2-vCPU guest), and the limit is additionally enforced on the host via a CFS quota on the VM’s vCPU threads. Fork and snapshot preserve multi-vCPU state.

Docker inside the pod

The guest agent (PID 1) supervises a pinned dockerd (27.5.1, with Compose and Buildx on PATH) whose overlay2 graph lives on a dedicated per-VM virtio-blk scratch disk; the binaries come from a read-only, host-enforced payload slot at /run/k7d/docker-tools. Nothing is added to the pod spec and no container is privileged. Because it is a guest service rather than a CRI sidecar, a warm fork keeps dockerd and its running containers (the child’s graph disk is a reflink). Any value other than "true", or docker-disk without docker, fails Create. docker.sock is guest root — read Docker as a guest service before enabling it for untrusted workloads, and prefer k7-fc for multi-tenant use. K7 exposes this as k7 create --docker.

Sizing writable storage

A container’s writes go to an overlay upper on the sandbox’s scratch disk — a sparse ext4 image on the host, attached as a read-write virtio-blk device — not to guest RAM. Writable capacity is therefore independent of the pod’s memory limit, and filling it gives the writing process an ordinary ENOSPC rather than guest-wide memory pressure. Size it per pod (default 8 GiB):
That image is shared by the pod, so one container filling it also fails its siblings. To scope the blast radius, give individual containers their own image with a name=size list over container names:
Each named container gets a whole filesystem of its own, so an overrun is its own ENOSPC while siblings keep writing. Containers you don’t name go on sharing the pod’s scratch disk.
Per-container scratch is opt-in for two reasons: each image spends one of the VM’s virtio-MMIO device slots, and a pod that uses it is served by a fork rather than from the warm pool. A warm fork gets a reflink-CoW copy of every image, so a fork’s writes — and its usage numbers — are isolated from its parent and its siblings.
Kubernetes itself cannot see any of this: the CRI carries no ephemeral-storage figure in either direction, so resources.limits.ephemeral-storage never reaches the runtime and these annotations are the only sizing input. Usage is measured inside the guest and surfaced in the shim log, not in kubectl top.

Persistent data disks

Scratch dies with the pod. For state that must outlive the VM — a storage node’s replica directory, a database’s data dir — ask for named disks:
Each id:size:guest-mount entry attaches an ext4 image data/<id>.img on the daemon’s reflink filesystem, created and formatted on first use and reused as-is on every later attach. Sandbox teardown leaves it in place; only the daemon’s delete_data_disk op removes it. A size that disagrees with an existing image is an error, not a resize. A warm fork gets a reflink-CoW copy, so a fork’s writes diverge from the source’s without a byte copy. Like per-container scratch, every disk spends one virtio-blk device slot, which is why it is opt-in.

Annotation reference

All values fail loud on a malformed entry — a pod that asked for a disk and silently did not get one would lose its data with the VM, which is the failure the annotations exist to prevent.

Because VMs outlive shims

The daemon owns the VMs, so a containerd restart or shim crash does not kill your workloads: a freshly respawned shim resolves its sandbox back to the still-running VM via the daemon’s reattach_sandbox operation, which also verifies the guest agent still answers before handing the VM back. The daemon also exposes pause_vm / resume_vm for a sandbox VM: vCPU threads are stopped and device workers parked while guest memory, devices, and the vsock CID stay put. This is what K7’s k7 pause uses against the k7d backend.