
Runtime container e sandbox rootless che esegue immagini OCI applicate dal kernel in millisecondi, senza daemon, con profili delle risorse, allowlist seccomp e supporto Compose per codice non attendibile e generato dall'IA.
kern: Un sandbox veloce, rootless, e un runtime di risorse virtuali per qualsiasi carico di lavoro, incluso codice non fidato e generato da IA.
Un vero contenitore imposto dal kernel in ~3.5 ms, da un singolo binario da 1.52 MB senza daemon.
0 RAM a riposo · nessun daemon, nessuna socket, nulla da avviare · un singolo binario statico, libc come unica dipendenza Rust
# install the release binary (static, 1.52 MB, checksum-verified by the script)
curl -fsSL https://raw.githubusercontent.com/getkern/kern/main/install.sh | sh
# a throwaway shell in a real OCI image: rootless, kernel-enforced, a few ms
kern box dev --image alpine -it -- sh
Nessun Windows nativo: usa WSL2. Installa.
Un singolo binario che gestisce risorse, di cui l'isolamento è la prima. È per questo che non c'è una riga unica per kern in una tabella di confronto: è un container runtime, un sandbox, un partizionatore di risorse e un esecutore di stack allo stesso tempo, in 1.52 MB senza daemon.
pull, build da un Dockerfile, commit, push,
save/load. Una box da un'immagine parte in ~3.5 ms.--security-profile untrusted, è l'intero bundle indurito.vcpu:), memoria, disco (vdisk:) e dispositivi
(vgpio:), dichiarati una volta in un kern.toml e collegati per nome. kern run applica gli stessi limiti
a un processo sull'host, senza alcun sandbox. docs/RESOURCES.mdIl suo intero albero di dipendenze Rust è libc: i manifest JSON e OCI sono analizzati a mano, e pull
usa curl e tar già presenti sulla macchina invece di linkare uno stack TLS. (1.52 MB
è la build di release ottimizzata per le dimensioni; un semplice cargo install dai sorgenti è 1.91 MB.)
Non è un hypervisor. Il confine è il kernel Linux, quindi un bug di privilege escalation del kernel è una fuga. Docker e Podman condividono questa condizione, ed è per questo che esistono gVisor e Firecracker.
Letto insieme al tagline, è una riga vista da entrambi i lati: il codice non fidato e generato da IA è ciò per cui kern è stato FATTO, perché hai scelto di eseguirlo e di gestire tu il raggio d'esplosione (tool-call degli agenti, job CI, step di build, celle di codice). Non è invece per codice ostile di sconosciuti, multi-tenant, su un kernel su cui servi altri tenant. kern parte sempre rootless, mentre in Docker è opt-in.
Non è esente dal compromesso degli userns. Il suo isolamento si basa su un user namespace non privilegiato, una fonte prolifica di bug LPE del kernel. SECURITY.md lo dichiara prima di ogni altra affermazione.
Non è un muro attorno a ciò che monti dentro. -v $HOME:/host dà alla box la tua home directory: un
mount è una decisione di fiducia che prendi tu, non un confine che kern impone. --net host e --privileged
sono rinunce esplicite per nome. (L'unico percorso che kern si rifiuta di montare è il proprio registry runtime.)
Non è una reimplementazione di Docker Engine. Parla i formati di Docker, non la sua API: nessuna overlay network, nessun plugin, nessuno Swarm. Matrice: docs/DOCKER-COMPAT.md.
Non è un runtime Kubernetes. Nessun CRI. Usa containerd o CRI-O.
Non include slice GPU. Nella roadmap, senza codice GPU in questa edizione, quindi non c'è ancora nulla di cui fidarsi o da attaccare qui.
Ciò che non sa o non fa ancora è in OPEN_ITEMS.md invece di lasciartelo scoprire.
kern richiede un kernel Linux con user namespace non privilegiati e cgroup v2. Funziona su Linux, WSL2 e board ARM (Raspberry Pi · Jetson · Arduino UNO Q); non esiste nessuna build nativa per Windows, usa WSL2 (kern include una rootfs WSL preconfigurata).
Il percorso più rapido è il binario di rilascio: un file statico, nessuna toolchain, e lo script ne verifica lo SHA256 prima di installarlo.
curl -fsSL https://raw.githubusercontent.com/getkern/kern/main/install.sh | sh
Sceglie x86_64 o aarch64 per te, installa in ~/.local/bin (/usr/local/bin come root, o
KERN_INSTALL_DIR), e rifiuta di installare un download la cui somma di controllo non corrisponde. Verificare
a mano invece è di due righe:
curl -fsSLO https://github.com/getkern/kern/releases/latest/download/kern-x86_64-unknown-linux-musl.tar.gz{,.sha256}
sha256sum -c kern-x86_64-unknown-linux-musl.tar.gz.sha256 && tar xzf kern-x86_64-unknown-linux-musl.tar.gz
Dai sorgenti è l'altra via, e l'intero albero delle dipendenze è una sola crate (libc), quindi è
breve: clone, build e install hanno richiesto 36 s su un desktop (i7-14700KF), di più su una piccola board ARM.
# if you do not have Rust yet
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo install --git https://github.com/getkern/kern getkern --locked
Questo mette kern in ~/.cargo/bin, che rustup aggiunge al tuo PATH (apri una nuova shell, o
source "$HOME/.cargo/env", se kern non viene trovato).
Il rilascio include anche un binario aarch64, uno shim Windows .exe e una rootfs WSL preconfigurata, ciascuno
con il proprio .sha256; il tag è firmato GPG e timestampato in modo indipendente (provenance/).
kern doctor ti dice se le box funzioneranno qui prima di provarci. Board, WSL2 e la forma estesa:
docs/INSTALL.md. Domande comuni (Docker, bubblewrap, youki, E2B, Windows, il
modello di minaccia): docs/FAQ.md.
kern box dev --image alpine -it -- sh # a throwaway shell in a real OCI image
kern run --memory 256M --cpus 0.5 -- ./crunch # cap a process, no sandbox
kern box svc --image nginx:alpine -d -p 8080:80 \ # a service: published, restarted, health-checked
--restart --health-cmd 'wget -qO- localhost:80' -- nginx -g 'daemon off;'
kern ps # what is running, with PORTS and HEALTH
kern exec svc -it -- sh # shell into it
kern stop svc # its signal, its grace, then the code it exited with
kern top # live TUI: boxes, CPU/RAM, profiles, volumes
kern compose stack.toml up # a multi-box stack (examples/) or a compose.yml
kern compose stack.toml down # and take it down again
Codice non fidato, un solo flag per il bundle:
kern box job --image python:3.12-slim --security-profile untrusted --memory 256m \
-v ./job:/w -- python3 /w/x.py
--security-profile untrusted è la seccomp allowlist + --cap-drop ALL + --read-only in un unico
flag opt-in (puoi scriverli a mano se preferisci); aggiungi --require-limits per rifiutare l'avvio a meno che i
limiti di memoria/pids siano effettivamente applicati. Nessuna rete a meno che non la si chieda, capability pericolose rimosse,
seccomp sempre attiva. Novanta esempi eseguibili, ognuno che fa una cosa: examples/.
Ogni verbo di lettura risponde anche in JSON, quindi nulla deve fare il parsing di una tabella:
kern ps --json | jq '.[] | select(.health == "unhealthy") | .name'
kern volume ls --json # ps · images · stats · inspect · builds · pod ls · config list · diff
kern parla docker-compose.yml. Puntalo allo stack che hai già e kern compose up lo esegue
senza daemon e senza Docker Desktop, allo stesso modo su Linux, WSL2 e board ARM.
# compose.yaml - a real stack, unchanged
services:
db:
image: postgres:alpine
environment: { POSTGRES_PASSWORD: secret, POSTGRES_DB: app }
web:
image: adminer
ports: ["8080:8080"]
depends_on: [db]
kern compose compose.yaml up
Entrambe le immagini ufficiali partono, web raggiunge db per nome del servizio, e la porta viene pubblicata sull'host.
A caldo (immagini in cache) il tier web risponde in ~0.3 s, e lo stack costa solo ciò che postgres e adminer
usano realmente (~66 MB qui) con zero daemon in aggiunta, laddove Docker Desktop è una VM in background prima del tuo
primo container.
Le immagini ufficiali che passano a un utente non-root (postgres, redis, ...) richiedono uidmap e una riga
/etc/subuid, e i pull di immagini in uscita richiedono pasta; entrambi sono un apt install su una macchina di sviluppo, e kern doctor
segnala l'uno o l'altro se manca. Questo è il loop di sviluppo locale, non un orchestratore di produzione: niente Swarm, niente
overlay network.
Esegui codice generato da agenti o LLM dal tuo programma con
kern-sandbox, un wrapper sottile e senza dipendenze sopra il binario kern.
Ogni chiamata viene eseguita in una box isolata e nuova: rete spenta, limiti di memoria e pid, capability
rimosse, output limitato, e un timeout imposto dal binding stesso.
pip install kern-sandbox # PyPI · needs the `kern` binary above, on PATH or $KERN_BIN
npm install kern-sandbox # npm · same
from kern_sandbox import run_code
r = run_code("import platform; print(platform.python_version())")
print(r.stdout) # ran in a fresh box; a timeout / OOM / blocked escape is data on r.fault
Sandbox mantiene uno workspace tra le chiamate e
un kernel() caldo mantiene un interprete per celle sub-millisecondo (isolamento più debole, per scelta).display() e le figure matplotlib
tornano catturate, come una cella di notebook.kern-mcp): un server stdio senza dipendenze che dà a Claude Desktop,
Cursor o qualsiasi client MCP un interprete di codice locale. Punta il client su di esso:{ "mcpServers": { "kern": { "command": "kern-mcp" } } }
Strumenti: run_code (python/bash/node), write_file, read_file, list_files. Ogni chiamata è una
box nuova senza rete; i file persistono tra le chiamate in uno workspace su disco. Comando di setup, immagine e le
altre opzioni: bindings/python/README.md.
API completa, Python e Node: bindings/python/README.md · bindings/node/README.md.
Una slice viene dichiarata una volta in ~/.config/kern/kern.toml e collegata per nome, a una box sandboxed o a un
processo nudo, con lo stesso token.
Tre tipi: vcpu: (CPU e memoria), vdisk: (un disco scratch con cap di dimensione) e vgpio: (nodi
dispositivo). Due di essi, e gli anchor da cui sono ricavati:
[[cpu]] # the host budget a slice is carved from
id = "cpu:0"
cores = 8.0
[[vcpu]] # 1.5 cores and 512 MiB -> attach as vcpu:heavy
name = "heavy"
backend = "cpu:0"
cpus = 1.5
memory = "512m"
[[gpio]] # a controller anchor
id = "gpio:0"
[[vgpio]] # exactly one device node -> attach as vgpio:sensor
name = "sensor"
backend = "gpio:0"
i2c = ["/dev/i2c-1"]
kern validate ~/.config/kern/kern.toml # check it before anything runs
kern box train --image alpine vcpu:heavy vdisk:scratch -- ./train.sh
kern run vcpu:heavy -- ./train.sh # the same slice, no sandbox
kern box iot --image alpine vgpio:sensor -- ls /dev
I profili si compongono: più profili si collegano a una box, e un flag esplicito vince sul valore del profilo. Ogni
chiave è scritta come il suo flag CLI, quindi cpus è --cpus e memory è --memory. Un backend che nomina
un pool non dichiarato viene rifiutato quando la config viene letta, non quando la box parte.
docs/RESOURCES.md ha lo schema campo per campo.
Un vdisk: è una tmpfs supportata da RAM quando kern gira rootless, qualunque cosa dica il suo backend, e un'
immagine ext4 su loop con una quota reale quando gira privilegiato. kern dice quale hai ottenuto, per
profilo, invece di lasciartelo supporre, e il cap di dimensione è applicato in entrambi i casi.
vgpio: è granulare a livello di chip, non per linea. Chiedere pins binda l'intero /dev/gpiochipN, e
quel device a caratteri espone ogni linea di quel controller. pins = [17] non restringe la box
alla linea 17: il kernel non ha un confine di mount per linea, quindi l'elenco dei pin è un metadato collaborativo
piuttosto che un confine. Nominare un device node, come fa i2c sopra, concede quel nodo e nient'altro.
Intel i7-14700KF, Linux 7.0.0, il binario di rilascio, uno script che puoi eseguire tu stesso:
python3 examples/benchmark.py. I tuoi risultati differiranno in base a CPU, kernel e filesystem.
Tremila contemporaneamente richiedono ~2.2 s, e una box viva costa ~0.3 MB di memoria.
Due note oneste. Nessuno vince del tutto sulla latenza a colpo singolo: il minimo per unshare + exec è
da 1 a 2 ms, quindi l'intera fascia alta rientra nel proprio rumore, e bubblewrap è un launcher senza
immagini, cap o ciclo di vita. Il divario che conta è verso i motori, due ordini di grandezza
sopra.
Metodo, dettaglio per fase, numeri sulle board e ogni avvertenza: BENCHMARKS.md.
Namespace, un pivot_root, 16 capability pericolose rimosse prima di exec, una seccomp
allowlist sempre attiva di default (il filtro predefinito di moby meno le 35 syscall di fuga di kern, che restano
hard-killed; una syscall al di fuori del set verificato restituisce ENOSYS, e la denylist più ampia è l'opt-out
via KERN_SECCOMP=denylist), limiti cgroup v2 (--require-limits rifiuta di avviare se non sono vincolanti),
e un /dev deny-by-default. Dove un confine è cooperativo piuttosto che imposto dal kernel,
SECURITY.md lo dice e nomina il bypass.
Non devi prenderlo per fiducia: pentest/ contiene quattro suite adversarial che affermano quei confini contro il kernel piuttosto che contro i resoconti di kern, e girano senza un account registry o una rete.
sh pentest/run-with-local-registry.sh ./target/release/kern pentest/pentest-ports.sh
Segnala una vulnerabilità privatamente tramite GitHub Security Advisories o [email protected].
Il core è completo. Tutto quanto sopra funziona oggi: 840 test Rust, 78 Python e 61 Node,
pulito con clippy, pulito con cargo-deny, su hardware reale: Linux, WSL2, Raspberry Pi 5, Jetson Orin Nano,
Arduino UNO Q. v0.7.0 è la prima release pubblicata. La superficie CLI e di configurazione può ancora
cambiare, sempre segnalato in CHANGELOG.md.
Issue e pull request sono benvenute. CONTRIBUTING.md contiene il flusso di lavoro e i criteri; i contributi sono coperti dal CLA.
Alex, @realexhub. I commit arrivano da @getkerndev, l'identità di commit del progetto.
I commit non sono firmati; il TAG di rilascio sì. È questo che devi verificare:
git verify-tag v0.7.0 contro la chiave in provenance/, la cui impronta è in
SECURITY.md.
Apache-2.0. Vedi LICENSE e TRADEMARK.md.
kern compose <file> up accetta un kern-compose.toml
(tabelle [box.NAME], con i profili di risorse di cui sopra) o il docker-compose.yml che hai già,
letto così com'è. Uno stack a un pod, servizi che si raggiungono per nome.ps, logs, exec, stats, inspect, wait, top (una TUI live),
doctor, più un SDK Python e Node e un server MCP per agenti.| kern | Docker | Podman |
|---|
| Daemon | no | sì (dockerd + containerd) | no |
| Rootless | sì, sempre | opt-in | sì |
| Avvio a freddo, box nuda | ~2.3 ms | ~297 ms | ~293 ms |
| Avvio a freddo, da un'immagine OCI | ~3.5 ms | ~297 ms | ~293 ms |
| Stop di un servizio (init gestisce SIGTERM) | ~1.9 ms | ~310 ms | ~380 ms |
| Memoria residente, nulla in esecuzione | 0 | da 154 a 160 MB | 0 |
| Ingombro | un singolo binario da 1.52 MB | stack di daemon | installazione multi-binario |
| Immagini OCI, pull / build / push | sì | sì | sì |
docker-compose.yml | sì, letto così com'è | sì | parziale |
| Overlay network, Swarm, CRI | no | sì | parziale |
| GPU | nella roadmap | sì | sì |
| kern | bubblewrap | runc | podman | docker |
|---|
| Avvio a freddo (box nuda) | ~2.3 ms | ~2.3 ms | ~18.6 ms | ~293 ms | ~297 ms |
| 200 box in parallelo | ~0.11 s | ~0.16 s | ~0.35 s | ~44.8 s | ~16.2 s |
| docs/INSTALL.md | installazione su Linux, WSL2 e board ARM, dai sorgenti |
| docs/DOCKER-COMPAT.md | cosa di Docker funziona, cosa no, e dove differisce |
| docs/RESOURCES.md · docs/CONFIG.md · docs/STORAGE.md · docs/EGRESS.md | il modello a due verbi, lo schema kern.toml, volumi ed egress |
| docs/THREAT_MODEL.md · SECURITY.md · OPEN_ITEMS.md | il modello di minaccia (strutturato, poi per meccanismo), e le lacune note |
| BENCHMARKS.md · EDGE.md | misurazioni, ed esecuzione su un Pi, Jetson o UNO Q |
| examples/ · blog/ | novanta script eseguibili, e approfondimenti più lunghi |
| bindings/python/README.md · bindings/node/README.md | l'SDK kern-sandbox: incorpora kern in Python o Node |