# k7 has four sandbox backends, no default, and persistence on exactly one

> A self-hosted stack that runs untrusted code inside microVMs on your own Kubernetes cluster, wrapping Kata with Firecracker, QEMU, Longhorn and a custom runtime. The comparison table on its own README is the most useful page here, footnotes included.

**Katakate/k7** — Your own self-hosted infra for lightweight VM sandboxes to safely execute untrusted code. CLI, API, Python SDK. ⭐ Star it if you like it! ⭐

- Repository: https://github.com/Katakate/k7
- Website: https://docs.katakate.org
- Stars: 810 · Forks: 25
- Language: Python
- License: Apache-2.0
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/katakate-k7

## There is no default backend and you choose twice

The install command takes the backend as a required argument with no fallback, and the accepted values are four: the Kata and Firecracker pair, the Kata and QEMU pair, the custom runtime, and the custom runtime with a Firecracker jailer. A fifth value, none, installs the cluster with no sandbox runtime at all, and the page says that is for scheduling-only masters, set through an inventory variable. Choosing again happens per sandbox, because each creation names its own runtime. So the backend is a property of the cluster for what gets deployed and a property of each sandbox for what actually runs. That double choice is deliberate, and it is also the thing most likely to surprise someone who expects a single default runtime for the whole cluster.

## Persistence exists on one backend and state otherwise rides the fork

The last row of the backend comparison is the one that changes how you design around the tool. Only the QEMU path, backed by Longhorn volumes, gives a sandbox that outlives the pod that created it, with snapshots and restore doing the work. The other three are marked as having no cross-pod persistence, and the Firecracker-based and custom-runtime paths substitute something else for it: the fork carries the state instead. That is a coherent design rather than a gap, because a warm copy-on-write fork of disk and memory gives you the state without the storage layer, but it is a different mental model. If your workload is a batch of similar jobs, state-in-the-fork is convenient. If your workload is a long-lived service that restarts, the QEMU row is the only one that answers that.

## The fork numbers are not the same measurement

Read the two fork figures together, because they are measuring different things. The custom runtime quotes roughly 5 milliseconds for a copy-on-write fork of disk and memory, and then quotes roughly 2.4 seconds for the same thing end to end through the CLI and Kubernetes, measured to a pod that is ready and accepting a command. The QEMU path quotes 46.7 seconds for a fork that is a disk clone plus a cold boot. So the millisecond figure is a property of the virtual machine monitor and says nothing about how long you wait, while the 2.4 second figure is the one you would feel. The custom runtime with the jailer is quoted at about 2.5 seconds end to end on the same mechanism, with the note that the child's container storage stays on the overlay driver it already had.

## The fastest-looking column has the weakest data

One cell in the create-to-ready row reads not re-measured rather than a number, and the footnote explains why: that backend needs a spare raw disk for its thin-pool storage and the node used for the lifecycle benchmark did not have one. The number offered instead comes from a Show HN post, which measured that backend at 3.74 seconds from create to executing a command, and which also recorded fork as not available because it was rejected. So the Firecracker column mixes a number from a different run, on different hardware, with an outright gap in one row. The methodology note says every other figure is a median of three runs on a single Hetzner AX41 node, with ranges in a separate performance document, which is the right amount of caution for a table used to pick infrastructure.

## A resume hang is documented inside the table

One footnote records a failure rather than a measurement, and it is worth knowing before you rely on the pause and resume row. For the custom runtime with the Firecracker jailer, the monitor's pause and resume returns immediately, but a command executed inside the container after resuming hung on the run being described, and the recorded reason is that the guest identifier was retained. That entry points at a numbered item in a challenges file at the repository root, so the failure is tracked rather than glossed over. The custom runtime without the jailer does not have the same problem in that row, and the footnote gives its resume-to-command time as 0.3 seconds, with the memory of the paused virtual machine surviving rather than the sandbox being scaled to zero and rebuilt.

## One flag, three different meanings of Docker

The Docker flag behaves differently depending on which backend you named, and that is the detail most likely to cost time. On the two custom-runtime backends you get a Docker daemon running inside the guest, with its storage on a virtual block device, and the result is forkable, so a forked sandbox has a working Docker and its images. On the Kata backends the same flag injects a privileged vehicle container instead, again with overlay storage on a block disk, and then the two paths diverge again: the QEMU path persists and forks that storage graph, while the Firecracker path is ephemeral and the images go when the sandbox does. An older spelling of the flag, with sidecar in place of docker, is explicitly marked as a deprecated alias rather than removed. The reasoning behind the difference is that in-guest Docker needs no host privilege, while the vehicle container does.

## Two Python distributions, two names, two version floors

The repository publishes two packages from one tree, and they do not agree on anything important. The main one is named k7 at version 0.4.0, requires Python 3.10.11 or newer, and depends on eleven things including an ASGI server with the standard extras, an async Kubernetes client, a validation library, a terminal formatting library and a command line framework. Its wheel bundles three import packages, one of which is the older name for the SDK and is described elsewhere as deprecated. The SDK is packaged separately through setup.py, also at 0.4.0, and its floor is Python 3.8 with a single dependency, a synchronous HTTP client, plus an extra for the async client under two names, one of which is kept for backwards compatibility. So the documented install for the SDK is a pinned 0.4.0 while the CLI package carries eleven dependencies, and one interpreter can satisfy the SDK and not the rest.

## A shell linter globs a directory the tree does not have

The build file defines its shell scripts as anything matching a name pattern under three directories: the source tree, a utilities directory, and a root filesystem build directory. Of those three, the last one does not appear in the listing of this repository's root, which holds the source directory, tests, documentation, tutorials, benchmarks, packaging and Debian directories, but no rootfs build directory. The find is written to tolerate a missing path rather than fail, so nothing breaks; it just silently lints two directories instead of three, or three instead of two depending on what a checkout contains. The rest of the build file is careful in a way that makes this stand out: the shell runs with error-on-failure and pipe-failure enabled, privilege escalation is used only when the current user is not already root, and the build and install steps name explicit scripts with no fallback lookup.

## Conclusion

Use k7 if you need to execute code you do not trust, on infrastructure you control, and you have a Linux host with hardware virtualisation plus the patience to choose a backend on purpose. Two cautions. The fastest backend is the one with the least complete benchmark data, so treat the numbers as medians of three runs on a single node rather than as guarantees. And the project describes itself as beta and under security review with advice to be cautious on sensitive workloads, which is not a formality to skip. Pick the QEMU and Longhorn path if you need a sandbox to survive independently of the process that created it.

## FAQ

### Which sandbox backends does k7 support?

Four at install time: the Kata and Firecracker pair, the Kata and QEMU pair, the k7d custom runtime, and k7d with a Firecracker jailer. A fifth value, none, installs the cluster without a sandbox runtime for scheduling-only masters, and each sandbox names its own backend when created.

### Which k7 backend keeps a sandbox after the pod is gone?

Only the QEMU path backed by Longhorn volumes, using named snapshots and restore. The other three have no cross-pod persistence and carry state in the fork instead.

### How fast is the k7 warm fork?

About 5 milliseconds at the virtual machine monitor for the copy-on-write clone of disk and memory, and about 2.4 seconds end to end through the CLI and Kubernetes until the pod is ready and accepting a command. The QEMU path is quoted at 46.7 seconds for a disk clone and cold boot.

### Does Docker inside a k7 sandbox behave the same on every backend?

No. On the k7d backends you get an in-guest Docker daemon with overlay storage on a virtual block device and it stays forkable. On the Kata backends the flag injects a privileged vehicle container instead, where the QEMU path persists and forks the graph and the Firecracker path is ephemeral.

### What Python version does k7 need?

The main package requires 3.10.11 or newer. The SDK is packaged separately at the same version number with a floor of Python 3.8 and a single synchronous HTTP client as its only dependency, plus an extra for the async client.

### Is k7 ready for production workloads?

It describes itself as beta and under security review, with a note to use caution for highly sensitive workloads. Hardware virtualisation must be available on the host, checked by confirming the KVM device node exists, and cloud support varies by provider.

## Sources

- [Katakate/k7 on GitHub](https://github.com/Katakate/k7)
- [License: Apache-2.0](https://github.com/Katakate/k7/blob/main/LICENSE)
- [Project website](https://docs.katakate.org)
- [README](https://github.com/Katakate/k7/blob/main/README.md)
- [Releases](https://github.com/Katakate/k7/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/katakate-k7
