{"slug": "show-hn-kern-container-and-resource-runtime-in-a-1-5-mb-binary-no-daemon", "title": "Show HN: Kern – container and resource runtime in a 1.5 MB binary, no daemon", "summary": "Kern, a new open-source container and resource runtime, launches as a single 1.52 MB static binary with no daemon, capable of starting a kernel-enforced, rootless container in about 3.5 ms. The Rust-based tool, which depends only on libc, supports OCI images, Docker-compatible formats, resource profiles, and an MCP server for AI agents, targeting untrusted and AI-generated code workloads. Its creator positions it as a lightweight alternative to Docker and Podman, while acknowledging limitations such as reliance on user namespaces and lack of hypervisor isolation.", "body_md": "**kern:** A fast, rootless sandbox and virtual resource runtime for any workload, including untrusted and AI-generated code.\n\n**A real, kernel-enforced container in ~3.5 ms, out of one 1.52 MB binary with no daemon.**\n\n**0 RAM at rest** · no daemon, no socket, nothing to start · one static binary, `libc`\n\nits only Rust dependency\n\n```\n# install the release binary (static, 1.52 MB, checksum-verified by the script)\ncurl -fsSL https://raw.githubusercontent.com/getkern/kern/main/install.sh | sh\n\n# a throwaway shell in a real OCI image: rootless, kernel-enforced, a few ms\nkern box dev --image alpine -it -- sh\n```\n\nNo native Windows: use WSL2. Install.\n\n**One binary that manages resources, of which isolation is the first.** That is why there is no\nsingle row for kern in a comparison table: it is a container runtime, a sandbox, a resource slicer\nand a stack runner at once, in 1.52 MB with no daemon.\n\n**A real container.** Real OCI images:`pull`\n\n,`build`\n\nfrom a Dockerfile,`commit`\n\n,`push`\n\n,`save`\n\n/`load`\n\n. A box from an image starts in ~3.5 ms.**A sandbox, always rootless.** User, PID, mount, network, UTS and IPC namespaces, an overlay or read-only root pivoted in, a deny-by-default seccomp allowlist and cgroup v2 limits. One flag,`--security-profile untrusted`\n\n, is the whole hardened bundle.**Resource profiles, not just isolation.** CPU (`vcpu:`\n\n), memory, disk (`vdisk:`\n\n) and devices (`vgpio:`\n\n), declared once in a`kern.toml`\n\nand attached by name.`kern run`\n\napplies the same caps to a process on the host, with no sandbox at all.[docs/RESOURCES.md](/getkern/kern/blob/main/docs/RESOURCES.md)**Stacks, in kern's own format or in Docker's.**`kern compose <file> up`\n\ntakes a`kern-compose.toml`\n\n(`[box.NAME]`\n\ntables, with the resource profiles above) or the`docker-compose.yml`\n\nyou already have, read as written. One stack to one pod, services reaching each other by name.**The tools around them.**`ps`\n\n,`logs`\n\n,`exec`\n\n,`stats`\n\n,`inspect`\n\n,`wait`\n\n,`top`\n\n(a live TUI),`doctor`\n\n, plus a Python and Node SDK and an MCP server for agents.\n\nIts entire Rust dependency tree is `libc`\n\n: JSON and OCI manifests are parsed by hand, and `pull`\n\nshells out to the `curl`\n\nand `tar`\n\nalready on the machine rather than linking a TLS stack. (1.52 MB\nis the size-optimized release build; a plain `cargo install`\n\nfrom source is 1.91 MB.)\n\n-\n**Not a hypervisor.** The boundary is the Linux kernel, so a kernel privilege-escalation bug is an escape. Docker and Podman share that condition, which is why gVisor and Firecracker exist.Read with the tagline, that is one line seen from both sides: untrusted and AI-generated code is what kern is FOR, because you chose to run it and own the blast radius (agent tool-calls, CI jobs, build steps, code cells). What it is not for is hostile code from strangers, multi-tenant, on a kernel you serve other tenants from. kern does start rootless always, where Docker's is opt-in.\n\n-\n**Not free of the userns trade.** Its isolation is built on an unprivileged user namespace, a fertile source of kernel LPE bugs.[SECURITY.md](/getkern/kern/blob/main/SECURITY.md)states this before any claim. -\n**Not a wall around what you mount in.**`-v $HOME:/host`\n\ngives the box your home directory: a mount is a trust decision you make, not a boundary kern enforces.`--net host`\n\nand`--privileged`\n\nare opt-outs by name. (The one path kern refuses to bind is its own runtime registry.) -\n**Not a Docker Engine reimplementation.** It speaks Docker's*formats*, not its API: no overlay networks, no plugins, no Swarm. Matrix:[docs/DOCKER-COMPAT.md](/getkern/kern/blob/main/docs/DOCKER-COMPAT.md). -\n**Not a Kubernetes runtime.** No CRI. Use containerd or CRI-O. -\n**Not shipping GPU slices.** On the[roadmap](/getkern/kern/blob/main/ROADMAP.md), with no GPU code in this edition, so there is nothing here to trust or to attack yet.\n\nWhat it does not know or does not do yet is in [OPEN_ITEMS.md](/getkern/kern/blob/main/OPEN_ITEMS.md) rather than left for\nyou to find.\n\nkern needs a Linux kernel with unprivileged user namespaces and cgroup v2. It runs on **Linux, WSL2\nand ARM boards** (Raspberry Pi · Jetson · Arduino UNO Q); there is **no native Windows** build, use\nWSL2 (kern ships a pre-baked WSL rootfs).\n\nThe quickest route is the release binary: one static file, no toolchain, and the script verifies its SHA256 before installing it.\n\n```\ncurl -fsSL https://raw.githubusercontent.com/getkern/kern/main/install.sh | sh\n```\n\nIt picks `x86_64`\n\nor `aarch64`\n\nfor you, installs to `~/.local/bin`\n\n(`/usr/local/bin`\n\nas root, or\n`KERN_INSTALL_DIR`\n\n), and refuses to install a download whose checksum does not match. Verifying by\nhand instead is two lines:\n\n```\ncurl -fsSLO https://github.com/getkern/kern/releases/latest/download/kern-x86_64-unknown-linux-musl.tar.gz{,.sha256}\nsha256sum -c kern-x86_64-unknown-linux-musl.tar.gz.sha256 && tar xzf kern-x86_64-unknown-linux-musl.tar.gz\n```\n\n**From source** is the other route, and the whole dependency tree is one crate (`libc`\n\n), so it is\nshort: clone, build and install took 36 s on a desktop (i7-14700KF), longer on a small ARM board.\n\n```\n# if you do not have Rust yet\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\ncargo install --git https://github.com/getkern/kern getkern --locked\n```\n\nThat puts `kern`\n\nin `~/.cargo/bin`\n\n, which rustup adds to your `PATH`\n\n(open a new shell, or\n`source \"$HOME/.cargo/env\"`\n\n, if `kern`\n\nis not found).\n\nThe release also ships an `aarch64`\n\nbinary, a Windows `.exe`\n\nshim and a pre-baked WSL rootfs, each\nwith its own `.sha256`\n\n; the tag is GPG-signed and independently timestamped ([provenance/](/getkern/kern/blob/main/provenance)).\n\n`kern doctor`\n\ntells you whether boxes will run here before you try. Boards, WSL2 and the long form:\n[docs/INSTALL.md](/getkern/kern/blob/main/docs/INSTALL.md). Common questions (Docker, bubblewrap, youki, E2B, Windows, the\nthreat model): [docs/FAQ.md](/getkern/kern/blob/main/docs/FAQ.md).\n\n```\nkern box dev --image alpine -it -- sh              # a throwaway shell in a real OCI image\nkern run --memory 256M --cpus 0.5 -- ./crunch      # cap a process, no sandbox\nkern box svc --image nginx:alpine -d -p 8080:80 \\  # a service: published, restarted, health-checked\n  --restart --health-cmd 'wget -qO- localhost:80' -- nginx -g 'daemon off;'\nkern ps                                            # what is running, with PORTS and HEALTH\nkern exec svc -it -- sh                            # shell into it\nkern stop svc                                      # its signal, its grace, then the code it exited with\nkern top                                           # live TUI: boxes, CPU/RAM, profiles, volumes\nkern compose stack.toml up                         # a multi-box stack (examples/) or a compose.yml\nkern compose stack.toml down                       # and take it down again\n```\n\nUntrusted code, one flag for the bundle:\n\n```\nkern box job --image python:3.12-slim --security-profile untrusted --memory 256m \\\n  -v ./job:/w -- python3 /w/x.py\n```\n\n`--security-profile untrusted`\n\nis the seccomp **allowlist** + `--cap-drop ALL`\n\n+ `--read-only`\n\nin one\nopt-in flag (spell them out by hand if you prefer); add `--require-limits`\n\nto refuse to start unless the\nmemory/pids caps are actually enforced. No network unless you ask, dangerous capabilities dropped,\nseccomp always on. Ninety runnable examples, each doing one thing: [examples/](/getkern/kern/blob/main/examples).\n\nEvery read verb also answers in JSON, so nothing has to parse a table:\n\n```\nkern ps --json | jq '.[] | select(.health == \"unhealthy\") | .name'\nkern volume ls --json          # ps · images · stats · inspect · builds · pod ls · config list · diff\n```\n\nkern speaks `docker-compose.yml`\n\n. Point it at the stack you already have and `kern compose up`\n\nruns it\nwith no daemon and no Docker Desktop, the same on Linux, WSL2 and ARM boards.\n\n```\n# compose.yaml - a real stack, unchanged\nservices:\n  db:\n    image: postgres:alpine\n    environment: { POSTGRES_PASSWORD: secret, POSTGRES_DB: app }\n  web:\n    image: adminer\n    ports: [\"8080:8080\"]\n    depends_on: [db]\nkern compose compose.yaml up\n```\n\nBoth official images start, `web`\n\nreaches `db`\n\nby service name, and the port is published to the host.\nWarm (images cached) the web tier serves in **~0.3 s**, and the stack costs only what postgres and adminer\nactually use (~66 MB here) with **zero daemon** on top, where Docker Desktop is a background VM before your\nfirst container.\n\nOfficial images that drop to a non-root user (postgres, redis, ...) want `uidmap`\n\nand a `/etc/subuid`\n\nline, and outbound image pulls want `pasta`\n\n; both are one `apt install`\n\non a dev box, and `kern doctor`\n\nnames either if it is missing. This is the local dev loop, not a production orchestrator: no Swarm, no\noverlay networks.\n\nRun agent or LLM-generated code from your own program with\n, a thin, dependency-free wrapper over the\n\n`kern-sandbox`\n\n`kern`\n\nbinary. Every call runs in a fresh isolated box: network off, memory and pid caps, capabilities\ndropped, output bounded, and a timeout the binding itself enforces.\n\n```\npip install kern-sandbox        # PyPI   · needs the `kern` binary above, on PATH or $KERN_BIN\nnpm  install kern-sandbox       # npm    · same\npython\nfrom kern_sandbox import run_code\n\nr = run_code(\"import platform; print(platform.python_version())\")\nprint(r.stdout)          # ran in a fresh box; a timeout / OOM / blocked escape is data on r.fault\n```\n\n**Faults are data, not exceptions**: a timeout, OOM-kill or blocked syscall is a field on the result, not a raise. A fresh box per call by default;`Sandbox`\n\nkeeps a workspace across calls and a warm`kernel()`\n\nkeeps one interpreter for sub-millisecond cells (weaker isolation, by choice).**Rich results without a Jupyter kernel**: the last expression,`display()`\n\nand matplotlib figures come back captured, like a notebook cell.- Ships an\n**MCP server**(`kern-mcp`\n\n): a dependency-free stdio server that gives Claude Desktop, Cursor or any MCP client a local code interpreter. Point the client at it:\n\n```\n{ \"mcpServers\": { \"kern\": { \"command\": \"kern-mcp\" } } }\n```\n\nTools: `run_code`\n\n(python/bash/node), `write_file`\n\n, `read_file`\n\n, `list_files`\n\n. Each call is a fresh\nnetwork-off box; files persist across calls in a workspace on disk. Setup command, image and the\nother options: [bindings/python/README.md](/getkern/kern/blob/main/bindings/python/README.md).\n\nFull API, Python and Node: [bindings/python/README.md](/getkern/kern/blob/main/bindings/python/README.md) ·\n[bindings/node/README.md](/getkern/kern/blob/main/bindings/node/README.md).\n\nA slice is declared once in `~/.config/kern/kern.toml`\n\nand attached by name, to a sandboxed box or a\nbare process, with the same token.\n\nThree kinds: `vcpu:`\n\n(CPU and memory), `vdisk:`\n\n(a size-capped scratch disk) and `vgpio:`\n\n(device\nnodes). Two of them, and the anchors they are carved from:\n\n```\n[[cpu]]                     # the host budget a slice is carved from\nid    = \"cpu:0\"\ncores = 8.0\n\n[[vcpu]]                    # 1.5 cores and 512 MiB  ->  attach as  vcpu:heavy\nname    = \"heavy\"\nbackend = \"cpu:0\"\ncpus    = 1.5\nmemory  = \"512m\"\n\n[[gpio]]                    # a controller anchor\nid = \"gpio:0\"\n\n[[vgpio]]                   # exactly one device node ->  attach as  vgpio:sensor\nname    = \"sensor\"\nbackend = \"gpio:0\"\ni2c     = [\"/dev/i2c-1\"]\nkern validate ~/.config/kern/kern.toml       # check it before anything runs\nkern box train --image alpine vcpu:heavy vdisk:scratch -- ./train.sh\nkern run vcpu:heavy -- ./train.sh            # the same slice, no sandbox\nkern box iot --image alpine vgpio:sensor -- ls /dev\n```\n\nProfiles compose: several attach to one box, and an explicit flag beats a profile's own value. Every\nkey is spelled like its CLI flag, so `cpus`\n\nis `--cpus`\n\nand `memory`\n\nis `--memory`\n\n. A backend naming\nno declared pool is refused when the config is read, not when the box runs.\n[docs/RESOURCES.md](/getkern/kern/blob/main/docs/RESOURCES.md) has the field-by-field schema.\n\nA `vdisk:`\n\nis a RAM-backed tmpfs when kern runs rootless, whatever its backend says, and an\next4-on-loop image with a real quota when it runs privileged. kern says which one you got, per\nprofile, rather than letting you assume, and the size cap is enforced either way.\n\n** vgpio: is chip-granular, not per-line.** Asking for\n\n`pins`\n\nbinds the whole `/dev/gpiochipN`\n\n, and\nthat character device exposes every line of that controller. `pins = [17]`\n\ndoes not restrict the box\nto line 17: the kernel has no per-line mount boundary, so the pin list is cooperative metadata rather\nthan a boundary. Naming a device node, as `i2c`\n\nabove does, grants that node and nothing else.| kern | Docker | Podman | |\n|---|---|---|---|\n| Daemon | no |\nyes (`dockerd` + `containerd` ) |\nno |\n| Rootless | yes, always |\nopt-in | yes |\n| Cold start, bare box | ~2.3 ms |\n~297 ms | ~293 ms |\n| Cold start, from an OCI image | ~3.5 ms |\n~297 ms | ~293 ms |\n| Stop a service (init handles SIGTERM) | ~1.9 ms |\n~310 ms | ~380 ms |\n| Resident memory, nothing running | 0 |\n154 to 160 MB | 0 |\n| Footprint | one 1.52 MB binary |\ndaemon stack | multi-binary install |\n| OCI images, pull / build / push | yes | yes | yes |\n`docker-compose.yml` |\nyes, read as-is | yes | partial |\n| Overlay networks, Swarm, CRI | no |\nyes | partial |\n| GPU | on the roadmap | yes | yes |\n\nIntel i7-14700KF, Linux 7.0.0, the release binary, one script you can run yourself:\n`python3 examples/benchmark.py`\n\n. Yours will differ with your CPU, kernel and filesystem.\n\n| kern | bubblewrap | runc | podman | docker | |\n|---|---|---|---|---|---|\n| Cold start (bare box) | ~2.3 ms |\n~2.3 ms | ~18.6 ms | ~293 ms | ~297 ms |\n| 200 boxes in parallel | ~0.11 s |\n~0.16 s | ~0.35 s | ~44.8 s | ~16.2 s |\n\nThree thousand at once take ~2.2 s, and a live box costs ~0.3 MB of memory.\n\nTwo honest notes. **Nobody wins single-shot latency outright**: the floor for `unshare`\n\n+ `exec`\n\nis\n1 to 2 ms, so the whole top tier sits inside its own noise, and bubblewrap is a launcher with no\nimages, caps or lifecycle. The gap that means something is to the *engines*, two orders of magnitude\nabove.\n\nMethod, per-phase breakdown, board numbers and every caveat: ** BENCHMARKS.md**.\n\nNamespaces, a `pivot_root`\n\n, 16 dangerous capabilities dropped before exec, an always-on seccomp\n**allowlist** by default (moby's own default filter minus kern's 35 escape syscalls, which stay\nhard-killed; a syscall outside the vetted set returns `ENOSYS`\n\n, and the wider denylist is the opt-out\nvia `KERN_SECCOMP=denylist`\n\n), cgroup v2 limits (`--require-limits`\n\nrefuses to start unless they bind),\nand a deny-by-default `/dev`\n\n. Where a boundary is cooperative rather than kernel-enforced,\n[SECURITY.md](/getkern/kern/blob/main/SECURITY.md) says so and names the bypass.\n\nYou do not have to take it on trust: [pentest/](/getkern/kern/blob/main/pentest) holds four adversarial suites that assert\nthose boundaries against the kernel rather than against kern's own reporting, and they run without a\nregistry account or a network.\n\n```\nsh pentest/run-with-local-registry.sh ./target/release/kern pentest/pentest-ports.sh\n```\n\nReport a vulnerability privately via GitHub Security Advisories or [hello@getkern.dev](mailto:hello@getkern.dev).\n\n|\n\n[docs/DOCKER-COMPAT.md](/getkern/kern/blob/main/docs/DOCKER-COMPAT.md)[docs/RESOURCES.md](/getkern/kern/blob/main/docs/RESOURCES.md)·[docs/CONFIG.md](/getkern/kern/blob/main/docs/CONFIG.md)·[docs/STORAGE.md](/getkern/kern/blob/main/docs/STORAGE.md)·[docs/EGRESS.md](/getkern/kern/blob/main/docs/EGRESS.md)`kern.toml`\n\nschema, volumes and egress[docs/THREAT_MODEL.md](/getkern/kern/blob/main/docs/THREAT_MODEL.md)·[SECURITY.md](/getkern/kern/blob/main/SECURITY.md)·[OPEN_ITEMS.md](/getkern/kern/blob/main/OPEN_ITEMS.md)[BENCHMARKS.md](/getkern/kern/blob/main/BENCHMARKS.md)·[EDGE.md](/getkern/kern/blob/main/EDGE.md)[examples/](/getkern/kern/blob/main/examples)·[blog/](/getkern/kern/blob/main/blog)[bindings/python/README.md](/getkern/kern/blob/main/bindings/python/README.md)·[bindings/node/README.md](/getkern/kern/blob/main/bindings/node/README.md)`kern-sandbox`\n\nSDK: embed kern in Python or Node**The core is done. Everything above works today:** 840 Rust, 78 Python and 61 Node tests,\nclippy-clean, `cargo-deny`\n\n-clean, on real hardware: Linux, WSL2, Raspberry Pi 5, Jetson Orin Nano,\nArduino UNO Q. **v0.7.0 is the first published release.** The CLI and config surface can still\nchange, always called out in [CHANGELOG.md](/getkern/kern/blob/main/CHANGELOG.md).\n\nIssues and pull requests are welcome. [CONTRIBUTING.md](/getkern/kern/blob/main/CONTRIBUTING.md) has the workflow and the\ngates; contributions are covered by the [CLA](/getkern/kern/blob/main/CLA.md).\n\nAlex, [@realexhub](https://github.com/realexhub). Commits come from\n[@getkerndev](https://github.com/getkerndev), the project's commit identity.\n\n**The commits are not signed; the release TAG is.** That is what to verify:\n`git verify-tag v0.7.0`\n\nagainst the key in [provenance/](/getkern/kern/blob/main/provenance), whose fingerprint is in\n[SECURITY.md](/getkern/kern/blob/main/SECURITY.md).\n\nApache-2.0. See [LICENSE](/getkern/kern/blob/main/LICENSE) and [TRADEMARK.md](/getkern/kern/blob/main/TRADEMARK.md).", "url": "https://wpnews.pro/news/show-hn-kern-container-and-resource-runtime-in-a-1-5-mb-binary-no-daemon", "canonical_source": "https://github.com/getkern/kern", "published_at": "2026-08-24 18:24:59+00:00", "updated_at": "2026-08-24 19:12:53.291859+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["Kern", "Docker", "Podman", "gVisor", "Firecracker", "OCI", "Rust", "MCP"], "alternates": {"html": "https://wpnews.pro/news/show-hn-kern-container-and-resource-runtime-in-a-1-5-mb-binary-no-daemon", "markdown": "https://wpnews.pro/news/show-hn-kern-container-and-resource-runtime-in-a-1-5-mb-binary-no-daemon.md", "text": "https://wpnews.pro/news/show-hn-kern-container-and-resource-runtime-in-a-1-5-mb-binary-no-daemon.txt", "jsonld": "https://wpnews.pro/news/show-hn-kern-container-and-resource-runtime-in-a-1-5-mb-binary-no-daemon.jsonld"}}