A fast, lightweight sandbox & virtual resource manager.
Run untrusted or agent-generated code in a real, kernel-enforced sandbox that starts in ~1.9 ms — a single ~1.5 MB rootless binary, no daemon. Runs everywhere Linux does: bare Linux, Windows (via WSL2), and ARM boards — Raspberry Pi, NVIDIA Jetson, Arduino UNO Q — where Docker won't even install. Embed it from Python or Rust, or drive it from the CLI.
~1.9 ms cold start (vs ~300 ms docker run) · ~1.5 MB static binary · 0 RAM at rest · rootless
Install · Quickstart · Docker compat · When to use · Embed (Rust / Python) · How it works · Benchmarks · Security
kern runs Linux workloads in real, kernel-enforced sandboxes — user + PID + mount + network + UTS + IPC namespaces, an overlay or read-only root pivoted in, an always-on seccomp filter, and cgroup limits. It pulls OCI images, builds them, runs them, and gets out of the way: no background daemon, one short-lived process per box, started in single-digit milliseconds.
It's built around one idea — virtual resources. A container is the first resource kern manages
(isolation); the same model extends to CPU, memory, disk (vdisk:) and GPIO (vgpio:) slices
today, and to GPU slices on the roadmap. A full daemonless container UX — OCI pull and build,
overlay, volumes, secrets, in-box SSH, cp/pause/attach, ps/exec/logs, compose, health,
tag/push — in ~1.5 MB.
kern box dev --image alpine -- sh # a throwaway, isolated Alpine shell — in a few ms…or embed it — a fresh, isolated box per call, for untrusted or agent-generated code (E2B/Firecracker territory, but local and ~1.5 MB — no cloud, no account, no VM):
import kern_sandbox as kern
r = kern.run_code("print(sum(range(100)))") # network OFF, hard caps, a timeout the binding enforces
print(r.stdout, r.success) # → a fresh 1.9 ms box, discarded after- ⚡ Daemonless & tiny. No
dockerd-style service. A ~1.5 MB static binary, one Rust dependency (libc) — it shells out to the system'scurl/taronly to pull images (running a box needs neither). Cold start ~1.9 ms vs ~300 ms fordocker run; ~7 MB RSS per box vs an always-on ~186 MB daemon (dockerd+containerd).kern psreads state straight from the kernel. - 👤 Rootless by default. Unprivileged user namespaces — your uid maps to root inside the box,
and only there. Single-uid is the default and is
libc-pure (no helper, smallest id surface);--uid-rangeopts into a full sub-id range (apt,www-data-style drops) via the standardnewuidmap+/etc/subuid— we state plainly that path is not helper-free. No host privilege is gained either way. - 🧱 Correct by construction. The mount sequence is a typestate: remounting the root read-only
before pivoting into it doesn't compile — a class of sandbox-escape bug is unrepresentable, not
just untested.
--planprints the exact isolation sequence without running anything. - 🔍 Honest about its boundaries. Filesystem / process / namespace isolation is a real kernel boundary — the right tool for your own or semi-trusted code (CI, dev, edge, your agents' code). For actively hostile multi-tenant code, reach for a microVM. SECURITY.md says exactly when to use which, and marks every guarantee that is cooperative or opt-in.
| Verb | Question it answers | What it does | Status |
|---|---|---|---|
kern box |
"Isolate this workload — and slice its resources." | Its own namespaces, overlay/read-only fs, private process tree, seccomp — the container — plus the same resource slices (--memory, --cpus, vcpu:, vdisk:, vgpio:). |
✅ works now |
kern run |
"Just slice resources — no sandbox." | Run a command against a CPU / memory quota with no isolation — the lean governor on its own. (A GPU slice is on the roadmap.) | ✅ works now |
Both take resource slices — the difference is the sandbox. box = isolation + slices; run =
slices without the sandbox. They compose — run inside box. Both ship today.
No daemon, no root — one ~1.5 MB binary.
kern box try --image alpine -- shDeny-by-default — only the peripheral you name crosses the boundary.
kern box iot --image alpine vgpio:sensor -- ./read.py # only /dev/i2c-1 crosses inServerless-style, on your own machine — one throwaway box per call.
echo "$payload" | kern box fn --image python -- handler.pyThe same box on a Pi or an Android-kernel board — just copy the one binary.
scp kern pi: && ssh pi 'kern box edge --image alpine -- ./agent'kern build -t app:1 . && kern tag app:1 registry.example/app:1 && kern push registry.example/app:1Daemonless, rootless, and complete — the full container UX plus resource slices, in one binary:
- 📦 Run anything, isolated — OCI images from any registry (v2 auth, multi-arch, gzip + zstd) or a
--rootfs; CoW overlay (image immutable, scratch discarded) or--read-only;-itTTY;--initPID-1 reaper. - 🎚️ Governed slices — hard cgroup-v2 caps on any
box/run:--memory·--cpus·--cpuset-cpus·--memory-swap-max·--pids-limit·--io-weight·--nice.kern runis the governor with no sandbox. - 💾 Data & devices —
-vvolumes (symlink-safe) · named volumes with a--sizequota · network volumes (nfs/smb/sshfs) ·--secret→/run/secrets(RAM,0400) ·vdisk:scratch ·vgpio:device passthrough (deny-by-default) ·--tmpfs. - 🌐 Network & identity — isolated by default;
--network hostfor outbound;-prootless publish (loopback unless you ask); in-box--ssh;--podshared-net pods (--no-outbound);--tun;--user. - 🔒 Least privilege — 13 dangerous caps always dropped (
--cap-add/--cap-drop); an always-on seccomp denylist (kexec, modules, ptrace, mount API,setns, …) that also kills wrong-arch + x86_64 x32-ABI aliases. - ♻️ Lifecycle, no daemon —
--restart+--health-cmd;cp/pause/attach/exec;ps/top/stats/logs/inspect/prune/gc/history/recover;compose(readsdocker-compose.ymltoo); reusable[[vcpu]]/[[vgpio]]/[[vdisk]]profiles;kern doctor.
Every flag & command, grouped
Run anything, isolated
- OCI images, any registry —
--image alpinepulls (registry v2, multi-arch → your arch, gzip and zstd layers) and runs. Docker Hub, GHCR, GitLab, quay, Harbor, self-hosted — via the standardWWW-Authenticatechallenge (Bearer or Basic).kern loginstores creds0600and passes them tocurloff-argv. Or bring a rootfs with--rootfs. Pull a foreign arch withkern pull --platform os/arch, then run it. - Governed slices —
kern runcaps a command with no sandbox;--memory/--cpus/--cpuset-cpus(pin) /--memory-swap-max/--pids-limit/--io-weight/--niceset hard cgroup-v2 caps on anyboxorrun(kern warns if a controller isn't delegated). - Writable by default — a copy-on-write overlay; the image stays immutable, scratch is discarded
on exit.
--read-onlyfor a read-only root. Interactive TTY with-it(raw mode, resize-aware). --init— a built-in PID-1 reaper (no zombies, forwards SIGTERM) without bundlingtini.
Data & devices across the boundary
- Volumes, full —
-v src:dst[:ro](symlink-safe) · named volumes (kern volumeCRUD, with a per-volume--sizequota) · network volumes (nfs:///smb:///sshfs://) mounted rootless via FUSE/GVFS. - Secrets —
--secret NAME=value/NAME=-(stdin) /SRC[:NAME](file) →/run/secrets/NAME(mode0400) on a RAM tmpfs, never in the image or env. - vDisk (
vdisk:) — a size-capped scratch at/vdisk/<name>: RAM tmpfs rootless, or an ext4-on-loop image (persistent, real quota) when privileged. - vGPIO (
vgpio:) — expose only the listed GPIO/I2C/SPI/LED peripherals into a box (deny-by-default for the rest) — for edge/IoT. --tmpfs PATH[:size]— a freshnosuid,nodevtmpfs (refused over hardened mounts).
Networking & identity
- Modes — isolated loopback-only by default (
--network none);--network host(=--net) for outbound;--hostname;--tunexposes/dev/net/tunfor WireGuard / userspace VPNs. - Port publishing —
-p [ip:]host:boxfrom a rootless forwarder; binds127.0.0.1by default,0.0.0.0only if you ask. - In-box SSH —
--ssh 2222runs a throwawaysshd(auto keypair or--ssh-key), published. - Pods —
kern pod create+--pod <name>: a shared-network pod where boxes reach each other by name (--no-outboundto deny internet egress). --user UID[:GID]— drop to a specific uid/gid (fails closed if unmapped).
Least privilege, configurable
- Capabilities — 13 dangerous caps always dropped;
--cap-drop CAP/ALLdrops more,--cap-add CAPkeeps one (still bounded by userns + seccomp). - Seccomp — an always-on denylist (kexec, kernel modules, ptrace, the mount API,
setns,syslog, …); wrong-arch and x86_64 x32-ABI syscalls are killed, closing the alias bypass. --privileged— opt-in, relaxes seccomp for a nestedkern box(docker-in-docker): re-allows exactlyunshare/setns/mount/umount2/pivot_root, keeps kexec/modules/bpf/io_uring/keyring blocked (stronger than Docker's--privileged), rootless-only. See SECURITY.md.
Lifecycle & operations, no daemon
- Stay-up & health —
--restartsupervises a detached box;--health-cmd+--health-interval/-retries/-start-period/-timeout/-actionprobe it;kern psshows HEALTH + PORTS. - Box ops —
kern cp(symlink-confined, CVE-2019-14271-safe),pause/unpause(freezer),attach(live output),exec(join a running box). - Observe & manage —
-ddetached;ps/top(TUI) /stats/logs/inspect/stop/kill/killall/prune/gc/history/recover. - Compose —
kern compose stack.toml(or adocker-compose.yml) brings up a multi-box stack in dependency order;kern up/downfor the file in this dir. - Diagnostics —
kern doctor(will boxes run here?),info,bench, shellcompletions. - Resource profiles — reusable
[[vcpu]]/[[vgpio]]/[[vdisk]]in~/.config/kern/kern.toml, attached by prefix (kern run vcpu:heavy vgpio:leds -- ./train.sh); managed withkern config.
Built-in hardening — user+PID+net+UTS+IPC+mount namespaces, self-pivot root, nosuid,nodev box
root, always-on seccomp, least-privilege caps, hard cgroup caps (via systemd-run where present);
every pulled blob sha256-verified and every layer vetted in-process (no ../absolute/device escapes,
decompression- & inode-bomb caps) before an isolated no-follow merge. Where a guarantee is cooperative
or opt-in (the GPU cap, vGPIO/vdisk trust scope, network volumes), SECURITY.md says so.
🐧 Linux & ARM boards (Raspberry Pi · Jetson · Arduino UNO Q) — one line; auto-detects x86-64 / aarch64:
curl -fsSL https://raw.githubusercontent.com/getkern/kern/main/install.sh | shServed from github.com (read the script first if you like). It downloads the release binary for
your arch and verifies the sha256 before installing. No Rust toolchain required. (getkern.dev/install.sh
is a short alias. On macOS, run it inside a Linux VM.)
🪟 Windows — one line in PowerShell (no Docker Desktop, no Ubuntu):
irm https://raw.githubusercontent.com/getkern/kern/main/install.ps1 | iexkern runs inside WSL2 — a real Linux kernel — so hard caps (--memory/--cpus) are enforced for
real. The installer ensures the WSL2 engine (self-elevating for the one reboot it may need, then
resuming on its own), imports kern's own pre-baked distro (a tiny Alpine + kern — no Ubuntu, no
manual steps), drops the kern.exe shim on your PATH, and verifies end-to-end. Every download is
sha256-checked. After it finishes: kern box dev --image alpine -it -- sh. Honest caveat: kern runs
inside the WSL2 kernel, so it doesn't shed the VM weight native Linux does — the win is "no Docker
Desktop", not "no VM".
📦 Offline / air-gapped (a board or locked-down server with no internet) — kern is a single ~1.5 MB static binary, so copying that one file is the install:
scp kern pi@raspberrypi:~/ # then: ssh pi@raspberrypi kern box dev --image alpine -- shNo daemon, no package, nothing to install on the target — which is why it runs where Docker can't (see EDGE.md).
Download + verify by hand, or build from source
# Straight from GitHub Releases, check the checksum yourself (aarch64: swap x86_64 → aarch64):
curl -fsSL https://github.com/getkern/kern/releases/latest/download/kern-x86_64-unknown-linux-musl.tar.gz \
| tar xz && install -m 755 kern ~/.local/bin/kern # a matching .tar.gz.sha256 ships next to it
# Or build it:
git clone https://github.com/getkern/kern && cd kern && cargo build --releaseA sandboxed shell from any OCI image. The image stays read-only; your writes go to a scratch overlay that vanishes on exit.
kern box dev --image alpine -it -- shHard memory + CPU limits (cgroup v2). kern run is the leanest path — a quota on a host command, no sandbox.
kern run --memory 256M --cpus 0.5 -- ./crunch-numbers
kern box build --image alpine --memory 512M --cpus 1.5 -v "$PWD:/src" -w /src --net -- makeDetached, a published port, restarts if it dies, health-checked — without a daemon.
kern box svc --image alpine -d -p 8080:80 --restart \
--health-cmd 'wget -qO- localhost:80' -- httpd -fkern ps # running boxes — with PORTS + HEALTH
kern top # live TUI: boxes, CPU/RAM, profiles
kern exec svc -it -- sh # shell into a running box
kern logs svc # its output
kern stop svc # stop it (kern stop --all for everything)Never baked into the image or env — delivered on a pipe, readable only inside the box.
printf "$DB_TOKEN" | kern box job --image alpine --secret TOKEN=- --cap-drop ALL \
-- sh -c 'curl -H "Authorization: Bearer $(cat /run/secrets/TOKEN)" https://api/…'kern doctor # will boxes even run on this host? preflight it
kern compose stack.toml # bring up a stack in dependency order (TOML or compose.yml)kern builds OCI images from a Dockerfile without a daemon — each RUN is a real kern box, each
step a content-addressed layer, reused on an unchanged rebuild.
kern build -t app:1 -f Dockerfile . # FROM / RUN / COPY / WORKDIR / ENV / CMD / ENTRYPOINT …
kern build -t app:1 --build-arg VER=9 . # build args; multi-stage (FROM … AS b; COPY --from=b)
kern tag app:1 registry.example/app:1 # give a cached image a second name
kern login registry.example # (private) — creds stored 0600
kern push registry.example/app:1 # publish as a single-layer OCI imageMulti-stage builds run each stage in its own box and confine COPY --from=<stage> to that stage's
filesystem (a hostile source path or symlink can't read the host). Layers pull as gzip or zstd.
push normalizes ownership and strips setuid/setgid, so an untrusted base can't smuggle a
privilege-bit into what you publish. (build/push are the newest surface — see
Project status.)
Run a sandboxed command straight from your program — a fresh isolated box per call (untrusted code, agent tools, per-request workers), structured result back.
Rust — the kern-isolation crate:
use kern_isolation::Sandbox;
let out = Sandbox::builder()
.rootfs("/var/lib/kern/rootfs/alpine")
.no_network() // isolated loopback-only netns
.memory_limit_bytes(256 << 20) // cgroup cap
.timeout_ms(5_000) // SIGKILL a runaway
.build()?
.run("python3", &["handler.py"])?;
assert!(out.success()); // + out.stdout / .stderr / .exit_code / .wall_msPython — the kern_sandbox package, built for "run this untrusted / agent-generated code safely":
import kern_sandbox as kern
# one-shot: throwaway box, network OFF, hard caps, a mandatory timeout the binding enforces
r = kern.run_code("print(sum(range(100)))")
print(r.stdout, r.success)
# a session: files persist across calls; deps installed once in the ONLY network-on step
with kern.Sandbox(image="python:3.12-slim", setup="pip install pandas",
memory_mb=512, cpus=1.0, timeout_s=30) as s:
s.write_file("data.csv", csv_bytes)
out = s.run_code("import pandas as pd; print(pd.read_csv('data.csv').describe())")
print(out.stdout) # network-off, capped, isolated; a fault is a typed SandboxFaultSafe by default — every relaxing argument (network=True, extra mounts) says so, and the binding owns
the timeout, so a timeout fault is a fact, not a guess. Both use the installed kern (PATH or
KERN_BIN) — see bindings/python and the kern-isolation crate (git/path, not yet
crates.io).
Linux, multi-architecture. Prebuilt static (musl) binaries for linux-x86_64 and
linux-aarch64 — one ~1.5 MB file, no Rust deps beyond libc (the pull path shells out to system
curl/tar).
| Platform | Arch | Status |
|---|---|---|
| x86_64 Linux | x86_64 | ✅ primary + automated CI |
| Windows 10/11 (via WSL2) | x86_64 | ✅ CI-built shim + distro (install.ps1) |
| NVIDIA Jetson (L4T) | aarch64 | ✅ manually validated |
| Raspberry Pi 5 | aarch64 | ✅ manually validated |
| Arduino UNO Q (Android kernel, Debian userland) | aarch64 | ✅ manually validated |
kern needs a Linux kernel with unprivileged user namespaces + cgroups v2, and a Linux
userland. The kernel flavor doesn't matter — kern runs even on an Android kernel with a Linux
userland (the Arduino UNO Q). On Windows, WSL2 is that Linux kernel — the one-line PowerShell
installer sets up WSL2 and drops in a pre-baked kern distro, so hard caps (--memory/--cpus) are
enforced for real (the honest caveat: you're inside the WSL2 VM, so it's "no Docker Desktop", not "no
VM"). kern does not run on stock Android-the-OS (Bionic, SELinux, userns off). Daemonless is a big
win on RAM-constrained boards (0 resident vs ~186 MB) — see EDGE.md. ARM CI is tracked
in the issues.
kern speaks Docker's formats, so your existing images and stacks just work — but it does not reimplement the Docker Engine API. It's a lightweight alternative, not a drop-in clone.
| From your Docker setup | kern |
|---|---|
| OCI images (Docker Hub, GHCR, quay, Harbor, self-hosted) | ✅ pull & run — multi-arch, WWW-Authenticate v2 auth, gzip + zstd |
docker-compose.yml |
✅ kern compose reads it — depends_on, healthcheck, deploy.resources.limits |
Dockerfile build |
✅ kern build — all common instructions, multi-stage, COPY --from=… (a build stage or an external image), BuildKit heredocs, --build-arg, layer cache. Daemonless: each RUN is a real box |
tag / push to a registry |
✅ kern tag / kern push |
Docker Engine API / docker.sock |
❌ — tools that attach to the socket (Docker Desktop, some IDE/CI plugins) won't connect |
| Swarm | ❌ — use compose / --pod |
✅ Use kern when you want:
- a fast, daemonless sandbox for your own or agent/LLM-generated code (fresh box per call, embeddable from Rust/Python);
- CI / build boxes, or a throwaway dev environment, without a background daemon;
- containers on edge / ARM boards where Docker won't even install (Pi, Jetson, Android-kernel);
- resource slices beyond containers —
vcpu:/vdisk:/vgpio:(GPU on the roadmap).
🔀 Reach for something else when you need:
- a hard boundary against actively hostile multi-tenant code → a microVM (Firecracker) or gVisor. kern's FS/PID/namespace/cgroup isolation is a real kernel boundary for your own or semi-trusted code, not a VM;
- the Docker Engine API / Docker Desktop workflow, or a true CLI drop-in → Docker / Podman;
- Kubernetes CRI integration → containerd / CRI-O.
kern states every boundary that is cooperative or opt-in plainly in SECURITY.md — being honest about the edges is the point.
A kern box is one short-lived process tree — no daemon, no shared state:
- Namespaces.
unshareinto a fresh user + PID + UTS + IPC namespace (and, by default, an isolated loopback-only net namespace;--netshares the host's — opt-in, flagged in the status panel). A single-UID map makes your uid root inside the box only;--uid-rangeopts into a full sub-id range. - Root filesystem. An overlay by default (image = read-only lower, a private upper takes
writes);
--read-onlyremounts it read-only after a self-pivot (pivot_root(".", ".")), which works even where a bind remount-RO is denied (some Android-kernel boards). Nothing is written into the rootfs, so many boxes share one read-only rootfs concurrently. (--bind-rootfsswaps the overlay for a direct bind — faster on a slow overlayfs, at the cost of a mutable shared source.) - Devices, volumes & secrets. A fresh
/devwith the safe nodes (+ /dev/net/tunon--tun);-vvolumes bound in with targets resolved symlink-safely, confined to the new root; secrets on a RAM/run/secrets(0400);vdisk:/vgpio:mounting exactly their declared disk/peripherals. - Lockdown. A clean env (no host secrets leak in), capabilities stripped to least-privilege, an
optional
--userdrop, an always-on seccomp denylist (incl. wrong-arch + x32), and cgroup caps — hardMemoryMax/CPUQuota/TasksMaxwhen a systemd user manager is present.
The whole mount sequence flows through a typestate (Rootfs<Mounted> → OldRootReady → ReadOnly):
the read-only remount is only reachable after the pivot, so getting the order wrong is a compile
error. The same sequence drives --plan.
OCI images pull with curl + tar (registry v2, WWW-Authenticate auth, multi-arch, gzip/zstd),
each blob sha256-verified, each layer vetted in-process from its raw tar headers (absolute/..
paths, device nodes, escaping hardlink/symlink targets, decompression- & inode-bomb caps) before it
extracts into isolated staging and merges no-follow — closed by parsing the layer, not by trusting
the host tar, so it holds on GNU tar and BusyBox tar alike. Every request is TLS-pinned; credentials
travel off-argv. See ARCHITECTURE.md.
One isolated /bin/true, warm image cache, one box per run. The x86_64 comparison is the 28-core,
Linux 6.17, NVMe, systemd-user host in BENCHMARKS.md (exact per-runtime commands
there). kern's figure is corroborated on a second machine — an Intel i7-14700KF — where a P-core-pinned
/bin/true box runs median 1.7 ms, avg 1.9 ms, best 1.6 ms (an E-core lands ~2.1 ms, same class). Your
numbers vary with hardware and load; board rows are from on-device runs.
| host | kernel | kern | bubblewrap | crun | runc | podman | docker |
|---|---|---|---|---|---|---|---|
| x86_64 desktop | 6.17 | 1.9 ms | 2.6 ms | 5.2 ms | 12.2 ms | 155 ms | 308 ms |
| Jetson Orin Nano | 5.15-tegra | 3.6 ms | 5.6 ms | ✗ | 32 ms | ✗ | 472 ms |
| Raspberry Pi 5 | 6.6-rpi | 2.1 ms | ✗ | ✗ | ✗ | ✗ | ✗ |
| Arduino UNO Q | 6.16 Android | 9.9 ms † | 14.9 ms | ✗ | 76 ms | ✗ | 858 ms |
✗ = not installed (nor readily installable) on that board. On the Pi 5, kern is the only runtime present at all — one ~1.5 MB static binary just works where the others are each a setup step (Docker alone is a ~186 MB daemon stack).
kern is the fastest sandbox here at ~1.9 ms (ahead of bubblewrap). Its own box setup is ~1 ms
(the KERN_TIMING phases — unshare + overlay + /dev + pivot + seccomp, each sub-ms); the rest is
process start + teardown. Adding a hard cgroup cap (the row above doesn't) brings it to ~6 ms — but
~4 ms of that is external systemd-run + D-Bus scope creation, not kern (systemd-run --user --scope -- true alone is ~4 ms), opt-out with KERN_NO_SCOPE (back to ~1.9 ms, best-effort in-process cgroup). The
top tier is all within a few ms — nobody wins single-shot latency outright. The real gap is to the
engines: ~80–160× faster than podman (~155 ms) / Docker (~308 ms), which round-trip a daemon every run — yet
kern alone ships a full daemonless container UX in ~1.5 MB. Beyond one start: ~500 boxes/s, ~7 MB
RSS/box, 0 resident (Docker keeps ~186 MB resident before you run anything).
† On the Arduino's Android kernel an overlayfs mount is ~31 ms (a kernel quirk — sub-ms elsewhere),
so the default overlay box is ~34 ms there; --bind-rootfs starts in 9.9 ms, ahead of bubblewrap.
Reproduce with examples/benchmark.py (auto-detects the runtimes you
have). kern does less than Docker (no overlay networks yet — see Roadmap); this compares
the run path.
Runnable, live-verified scripts in examples/:
| Scenario | Example |
|---|---|
| A guided tour — a tool, your code, resource caps, untrusted code, a service | showcase.sh |
| Try to break out — an adversarial isolation battery + 50 boxes at once | hardening.sh |
Safely vet an untrusted curl … sh install script (no net, no host access) |
safe-install-script.sh |
| Per-job pipeline: read-only input → isolated processing → output | data-pipeline.sh |
| Build/test a repo in a clean box (laptop or on-device) | ci-in-a-box.sh |
| Many isolated services on a small board (few MB vs a 186 MB daemon) | edge-many-services.sh |
Head-to-head timing: kern vs docker run |
compare-vs-docker.sh |
…plus governed runs, port-published services, compose stacks and more — see examples/README.md.
0.6.3 — a daemonless container + resource runtime that does less than Docker, on purpose.
Everything in Features works today and is tested (419 tests, clippy-clean,
cargo-deny-clean, security-audited slice by slice); the isolation is real. It deliberately skips a
lot Docker has (overlay networks, a plugin ecosystem) — the point is a small, fast, honest core. The
CLI and config surface are not frozen until 1.0.
New since 0.5: local image build (multi-stage, --build-arg, cached layers), tag +
push to any registry, zstd layer pull, --init PID-1 reaper, --platform select,
pods (--pod / --no-outbound), and the Python binding. build/push are the newest, deepest
surface — audited (COPY-from confinement, setuid/opaque hardening) and, where a rootless-overlay
kernel can't persist an opaque dir, they fail closed to a safe path rather than leak.
New in 0.6.3: a guided, "impossible to get wrong" profile editor in kern top (pick the devices
the host actually exposes; every typed field validated live against the same rule the save uses), and a
capability-based device deny-list for vgpio: — a /dev node that grants raw memory/storage or
host control (mem, disks, VFIO/DMA, kvm, HID injection, the console, …) is refused by kernel identity,
and each bound device is fd-pinned to close a check→mount race. Verified on all four boards, including
GPU-compute passthrough on the Jetson.
Deliberately not here yet: the headline GPU slices (on the Roadmap) and Docker-style overlay networking.
kern starts as a small, fast sandbox/OCI runtime and grows deliberately — the set of resources it governs is driven by what proves useful.
- Shipped: build + tag + push, zstd layers,
--init,--platform, pods, the Python binding; ongoing polish + broader (ARM) CI and edge/I/O ergonomics. - Windows, via WSL2 (shipped — one line: see Install). kern runs on Windows inside WSL2
— a real Linux kernel — so hard caps (
--memory/--cpus) are real there, verified. Akern.exeshim and a pre-baked kern WSL2 distro (Alpine + kern, no Ubuntu, no manual steps) install with a singleirm … | iexthat self-elevates, imports the distro and drops the shim on PATH. Both the shim and the distro are built by the release CI from tagged source and sha256-signed — the installer verifies every download, so it's the same trust level as the Linux binaries, not a hand-uploaded exe. Honest caveat: kern runs inside the WSL2 kernel, so it doesn't shed the VM weight native Linux does — the win is "no Docker Desktop", not "no VM". - GPU slices. A workload gets a slice of a GPU, not the whole device. It lands incrementally,
each stage useful on its own and each opt-in (
--no-gpustays the default): first safe access + visibility (device passthrough, driver-gated, sysfs/procfs masked; per-box VRAM + utilisation inkern stats), then a cooperative per-box VRAM cap via a userspace driver shim (NVIDIA/CUDA first — honest trust model: for first-party / noisy-neighbour isolation, not a hard boundary against a hostile tenant), then time-sliced compute + AMD (HIP) / Vulkan. A cross-vendor GPU merge pool stays an optional plugin, not core. - 1.0 — freeze: CLI + config under semver, threat model + architecture finalised.
See ARCHITECTURE.md for the design.
kern isolates with Linux namespaces + seccomp + a read-only userns root — a kernel-level boundary, deny-by-default on devices, with the host's sysfs/procfs masked. That's strong for first-party and noisy-neighbour workloads. For adversarial, multi-tenant untrusted code where you want a hardware-virtualization boundary, a microVM/VM adds a layer kern doesn't — a deliberate trade for ~1.9 ms starts and a ~1.5 MB footprint.
The full threat model, per-feature notes, and the honest "kern vs a microVM — when to use what" guidance live in SECURITY.md. Found a vulnerability? Report it privately via GitHub Security Advisories ("Report a vulnerability" on the repo) — please don't open a public issue.
Issues and PRs welcome — see CONTRIBUTING.md. Contributions are covered by a lightweight CLA; the project follows a Code of Conduct. Security reports: follow SECURITY.md (don't open a public issue).
Apache-2.0 — permissive, with an explicit patent grant. See NOTICE. The dependency
tree is deliberately tiny (libc only on the Rust side; standard-library-only on the Python side) and
copyleft-free — cargo deny check licenses is clean.
kern and getkern are trademarks of the project: the code is yours to use under Apache-2.0, but please don't ship a fork or a competing service under the name.