kern: a rootless container runtime for per-tool-call sandboxing
a fast, rootless sandbox and virtual resource runtime. Run any workload in a real container, including an agent's tool-call or AI-generated code.
At a glance
- What is it?
- kern is a single static Rust binary that starts OCI-image containers in milliseconds without a daemon, aimed at running untrusted code from agents and CI. The interesting part is not the speed claim; it is that typed faults, cgroup v2 caps and a deny-by-default seccomp profile are the default posture, not an add-on.
- Who is it for?
- Adopt kern if you are running model-generated code or agent tool calls and want one container per call without a daemon, or if you want cgroup v2 CPU, memory, disk and device caps declared once in kern.toml. Do not adopt it if your workloads are long-lived stateful services, if you need Windows containers, or if you are on a kernel without unprivileged user namespaces and cgroup v2.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem kern targets: isolation that costs less than the work it wraps
Running one container per agent tool call only makes sense if container startup is cheaper than the tool call itself. If it is not, you batch calls into a shared process and lose the isolation you wanted. kern's README puts a box from an OCI image at roughly 3.4 ms and quotes a 3.5 ms startup in its demo, measured on an Intel i7-14700KF under Linux 7.0. Treat those numbers as the project's own claim on its own hardware, not as a portable figure. The design point behind them is what matters: there is no daemon, no socket, and nothing resident at rest, so the cost is process setup rather than a round trip to a long-running service.
The audience is narrow and specific. It is people wiring an agent, a code interpreter, a notebook cell or a CI step who want the command the model just produced to run in a real container and then disappear. The README frames the same pattern for serverless style use, piping a request into a fresh box per request. If your workload is a long-lived service that you deploy and monitor, kern's per-call model is not aimed at you.
What actually enforces the boundary: namespaces, overlayfs, seccomp, cgroup v2
kern is rootless by construction. It composes user, PID, mount, network, UTS and IPC namespaces, pivots into an overlay or read-only root, applies a deny-by-default seccomp allowlist, and sets cgroup v2 limits. The README states that a single flag, --security-profile untrusted, bundles the hardened configuration rather than making you assemble it from individual switches. Network access is off unless requested, capabilities are dropped, and timeouts are applied from outside the box rather than by the code being run.
One detail is worth pausing on. The README says the entire Rust dependency tree is libc: JSON and OCI manifests are parsed by hand, and kern pull shells out to the curl and tar already on the machine instead of linking a TLS stack. That is a deliberate supply-chain trade. You get a very small binary and a very small audit surface, but image pulling depends on the host's curl and tar being present and behaving. On a minimal container image or a stripped-down CI runner, that dependency is the thing to check first.
The error model is the other half of the mechanism. Timeout, OOM-kill, blocked syscall and missing command are returned as typed faults next to stdout and the exit code, so a caller can branch on the failure kind instead of parsing a message. For an agent loop that needs to decide whether to retry, shrink the input, or give up, this is more useful than a non-zero exit status alone.
Getting it running: one install script, then kern box
The documented install is a shell script that selects x86_64 or aarch64, installs to ~/.local/bin, and refuses a download whose SHA256 does not match. KERN_INSTALL_DIR overrides the target directory. From source, the README gives cargo install --git https://github.com/getkern/kern getkern --locked. Windows goes through WSL2 with a PowerShell installer that sets WSL2 up for you, and macOS requires a Linux VM first, with colima named as one option before running the same install script inside the VM.
The core commands shown in the README are kern box dev --image alpine -it -- sh for a throwaway shell, and kern box app --image alpine -- echo hello from a real container for a one-shot run. Image handling covers pull, build from a Dockerfile, commit, push, and save/load. Around the boxes sit ps, logs, exec, stats, inspect, wait, top, and doctor. The Python and Node wrappers are separate packages, kern-sandbox on PyPI and npm, and the README states they need the binary installed first because they locate kern on PATH.
Resource profiles are declared in a kern.toml and attached by name, with vcpu:, vdisk: and vgpio: keys for CPU, disk and devices. The README's example attaches a profile with kern box train --image alpine vcpu:heavy vdisk:scratch, and the same profile can be applied to a host process with kern run vcpu:heavy -- ffmpeg, which skips the sandbox entirely. That last command is easy to misread. kern run is a cgroup cap, not an isolation boundary, unless you also pass --landlock-rw <path> to confine writes through the kernel's Landlock LSM. The distinction between kern box and kern run is the most important thing to get right before using either.
Where kern is the wrong tool
The boundary is the Linux kernel, and the README says so directly rather than burying it. That single sentence rules out Windows containers, and it means macOS users are running an ordinary Linux kern inside a VM. The VM is the isolation layer there, not kern, and colima, Lima, OrbStack or UTM becomes part of your operational surface.
The host requirements are equally concrete: a distribution with unprivileged user namespaces and cgroup v2. Kernels or hardened distributions that disable unprivileged user namespaces will not run this, and there is no fallback path described in the material. If you are on a shared host where you cannot control that sysctl, kern is not a candidate.
The third case is subtler. kern run applies caps with no sandbox at all, so anyone reaching for it as a lighter Docker is misreading it. And the per-call model is a poor fit for stateful services: if your workload needs to keep memory, connections or filesystem state between invocations, a box that starts and is deleted per call is the wrong shape. The README's own framing, a box per tool call or per request, is also a warning about where the design stops being appropriate.
Docker, Podman and the daemonless difference
The nearest comparison is Docker. Docker's architecture puts a long-running daemon between you and the container, and every docker run is a request to that daemon. kern removes the daemon entirely: the CLI is the runtime, so there is no socket to protect, no service to keep alive, and nothing resident when idle. That is what makes a few-millisecond startup plausible, and it is also why kern does not give you the things a daemon provides, such as a stable API for other tools to talk to or a central place to enforce policy across many callers.
Podman is the closer rootless comparison, since it already runs containers without a daemon and without root. The README's own framing of kern is wider than either: it describes kern as a container runtime, a sandbox, a resource slicer and a stack runner in one binary, which is why the project says there is no single row for it in a comparison table. The practical difference is that kern bundles resource profiles (vcpu:, vdisk:, vgpio:) and a compose-compatible stack command, kern compose <file> up, which accepts either a stack.toml in kern's own [box.NAME] format or an existing docker-compose.yml with no conversion step. If you already have a compose file, that is the migration path with the least rewriting.
Where Docker and Podman win is ecosystem surface. kern's image handling is pull, build, commit, push, save and load, which is a smaller set than the surrounding tooling most teams already have wired into CI.
Release cadence, licence and what that costs you
The release history in the repository shows v0.9.3, v0.9.31 and v0.9.32 within four days. The version numbers are pre-1.0 and the cadence is fast. For a component that sits underneath untrusted code execution, that is a real maintenance consideration: you are tracking a moving target, and pinning a version is the only way to keep behaviour stable. The README claims each release runs the full suite on Debian and Ubuntu, Fedora, the RHEL family, openSUSE, WSL2 and ARM boards, in real VMs and on the boards. That is the project's stated process, not something this article verified.
The licence is Apache-2.0, which is permissive and includes an explicit patent grant. That is the usual choice for infrastructure tooling and imposes no copyleft obligation on your own code. This is a description of the licence identifier, not legal advice; if you are redistributing kern or bundling it into a product, have your own counsel read the LICENSE file rather than taking this paragraph as clearance.
The dependency tree being libc alone is the strongest maintenance argument in kern's favour. A binary with one Rust dependency has very little to update when a transitive crate publishes a CVE, and the hand-rolled JSON and manifest parsing means there is no serde or TLS stack in the build. The cost is that pull depends on the host's curl and tar, so your update surface shifts from crates.io to the base image you run kern on.
The SDK path and the agent integration
The Python and Node packages are wrappers, not runtimes. The README states plainly that kern-sandbox finds kern on your PATH or where you point it, so the binary is a prerequisite and the packages add no isolation of their own. The Python binding integrates with LangChain in two places: as a code tool, and as an execution policy for LangChain's shell middleware. The README also lists any MCP client as a wiring option.
That two-way LangChain integration is the detail that separates kern from a general container runtime in this niche. Using it as a code tool is the obvious case. Using it as the execution policy for shell middleware is the more consequential one, because it means the middleware's shell calls inherit kern's network-off default, capability drops and seccomp profile rather than whatever the host process had. If you are already running an agent framework that shells out, that is the specific hook to evaluate.
What the material does not tell you is how the wrappers surface the typed faults. The README describes timeout, OOM-kill, blocked syscall and missing command as returned next to stdout and the exit code, but it does not show the Python or Node shape of that value. If your agent loop needs to branch on failure kind, confirm the wrapper's exception or result types before designing around them.
Editorial conclusion
Adopt kern if you are running model-generated code or agent tool calls and want one container per call without a daemon, or if you want cgroup v2 CPU, memory, disk and device caps declared once in kern.toml. Do not adopt it if your workloads are long-lived stateful services, if you need Windows containers, or if you are on a kernel without unprivileged user namespaces and cgroup v2. Verify three things before committing: that your kernel exposes unprivileged user namespaces, that /dev entries the vgpio profile would grant match what you expect, and that the typed fault output your code branches on is stable across the v0.9.x releases, which ship every day or two.
Community notes