
Runtime de contenedores rootless y sandbox que lanza imágenes OCI impuestas por el kernel en milisegundos, sin daemon, con perfiles de recursos, listas de permitidos de seccomp y soporte de compose para código no confiable y generado por IA.
kern: Un sandbox rápido sin root y un runtime de recursos virtuales para cualquier carga de trabajo, incluido código no confiable y generado por IA.
Un contenedor real, aplicado por el kernel, en ~3.5 ms, desde un único binario de 1.52 MB sin demonio.
0 RAM en reposo · sin demonio, sin socket, nada que iniciar · un único binario estático, libc como única dependencia de 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
Sin Windows nativo: usa WSL2. Instalación.
Un binario que gestiona recursos, de los que el aislamiento es el primero. Por eso no hay una única fila para kern en una tabla comparativa: es a la vez un runtime de contenedores, un sandbox, un troceador de recursos y un ejecutor de stacks, en 1.52 MB y sin demonio.
pull, build desde un Dockerfile, commit, push,
save/load. Una caja desde una imagen arranca en ~3.5 ms.--security-profile untrusted, es todo el paquete endurecido.vcpu:), memoria, disco (vdisk:) y dispositivos
(vgpio:), declarados una vez en un kern.toml y adjuntados por nombre. kern run aplica los mismos
límites a un proceso en el host, sin ningún sandbox. docs/RESOURCES.mdSu árbol de dependencias de Rust completo es libc: los manifiestos JSON y OCI se analizan a mano, y pull
delega en el curl y tar que ya están en la máquina en lugar de enlazar una pila TLS. (1.52 MB
es la build de release optimizada en tamaño; un cargo install directo desde el fuente es 1.91 MB.)
No es un hipervisor. La frontera es el kernel de Linux, así que un fallo de escalada de privilegios en el kernel es una vía de escape. Docker y Podman comparten esa condición, que es por la que existen gVisor y Firecracker.
Leído con el eslogan, es una línea vista desde ambos lados: el código no confiable y generado por IA es para lo que kern ESTÁ HECHO, porque tú eliges ejecutarlo y asumes el radio de explosión (llamadas a herramientas de agentes, trabajos de CI, pasos de build, celdas de código). Para lo que no sirve es para código hostil de desconocidos, multiinquilino, en un kernel desde el que das servicio a otros inquilinos. kern siempre arranca sin root, algo que en Docker es opcional.
No está libre de la concesión de userns. Su aislamiento se apoya en un namespace de usuario sin privilegios, una fuente fértil de bugs LPE en el kernel. SECURITY.md lo declara antes de cualquier afirmación.
No es un muro alrededor de lo que montas dentro. -v $HOME:/host da a la caja tu directorio home:
un mount es una decisión de confianza que tomas tú, no una frontera que aplique kern. --net host y
--privileged son renuncias explícitas por nombre. (La única ruta que kern se niega a montar es su propio
registro de runtime.)
No es una reimplementación de Docker Engine. Habla los formatos de Docker, no su API: sin overlay networks, sin plugins, sin Swarm. Matriz: docs/DOCKER-COMPAT.md.
No es un runtime de Kubernetes. Sin CRI. Usa containerd o CRI-O.
No incluye slices de GPU. En el roadmap, sin código de GPU en esta edición, así que no hay nada aquí que confiar o atacar todavía.
Lo que no sabe o aún no hace está en OPEN_ITEMS.md en lugar de dejarlo para que lo descubras.
kern necesita un kernel Linux con namespaces de usuario sin privilegios y cgroup v2. Funciona en Linux, WSL2 y placas ARM (Raspberry Pi · Jetson · Arduino UNO Q); no hay build nativa para Windows, usa WSL2 (kern incluye una rootfs WSL preconfigurada).
La vía más rápida es el binario de release: un único archivo estático, sin toolchain, y el script verifica su SHA256 antes de instalarlo.
curl -fsSL https://raw.githubusercontent.com/getkern/kern/main/install.sh | sh
Elige x86_64 o aarch64 por ti, instala en ~/.local/bin (/usr/local/bin como root, o
KERN_INSTALL_DIR), y se niega a instalar una descarga cuyo checksum no coincida. Verificarlo a mano
son dos líneas:
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
Desde el fuente es la otra vía, y el árbol de dependencias completo es un solo crate (libc), así que
es corto: clonar, compilar e instalar tardó 36 s en un escritorio (i7-14700KF), más en una placa ARM pequeña.
# 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
Eso coloca kern en ~/.cargo/bin, que rustup añade a tu PATH (abre una shell nueva, o
source "$HOME/.cargo/env", si kern no aparece).
El release también incluye un binario aarch64, un shim .exe para Windows y una rootfs WSL
preconfigurada, cada uno con su propio .sha256; el tag está firmado con GPG y sellado de tiempo de forma
independiente (provenance/).
kern doctor te dice si las cajas funcionarán aquí antes de que lo intentes. Placas, WSL2 y la versión
larga: docs/INSTALL.md. Preguntas frecuentes (Docker, bubblewrap, youki, E2B, Windows,
modelo de amenazas): 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
Código no confiable, un flag para todo el paquete:
kern box job --image python:3.12-slim --security-profile untrusted --memory 256m \
-v ./job:/w -- python3 /w/x.py
--security-profile untrusted es la allowlist seccomp + --cap-drop ALL + --read-only en un solo
flag opcional (puedes escribirlos explícitamente si prefieres); añade --require-limits para negarse a
arrancar salvo que los límites de memoria/pids estén realmente aplicados. Sin red salvo que lo pidas,
capabilities peligrosas eliminadas, seccomp siempre activo. Noventa ejemplos ejecutables, cada uno haciendo
una cosa: examples/.
Cada verbo de lectura también responde en JSON, así que nada tiene que parsear una tabla:
kern ps --json | jq '.[] | select(.health == "unhealthy") | .name'
kern volume ls --json # ps · images · stats · inspect · builds · pod ls · config list · diff
kern habla docker-compose.yml. Apúntale al stack que ya tienes y kern compose up lo ejecuta
sin demonio y sin Docker Desktop, igual en Linux, WSL2 y placas 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
Ambas imágenes oficiales arrancan, web alcanza a db por el nombre del servicio, y el puerto se publica
en el host. En caliente (imágenes en caché) el nivel web responde en ~0.3 s, y el stack cuesta solo lo
que postgres y adminer usan realmente (~66 MB aquí) con cero demonio encima, donde Docker Desktop es una
VM en segundo plano antes de tu primer contenedor.
Las imágenes oficiales que bajan a un usuario no root (postgres, redis, ...) quieren uidmap y una línea
/etc/subuid, y los pulls de imágenes salientes quieren pasta; ambos son un solo apt install en una
máquina de desarrollo, y kern doctor nombra el que falte. Este es el bucle de desarrollo local, no un
orquestador de producción: sin Swarm, sin overlay networks.
Ejecuta código de agentes o generado por LLM desde tu propio programa con
kern-sandbox, un wrapper fino y sin dependencias sobre el binario kern.
Cada llamada se ejecuta en una caja aislada nueva: red apagada, límites de memoria y pid, capabilities
eliminadas, salida acotada y un timeout que el propio binding aplica.
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 un workspace entre llamadas
y un kernel() en caliente mantiene un intérprete para celdas de submilisegundo (aislamiento más débil, por elección).display() y las figuras de matplotlib
regresan capturadas, como una celda de notebook.kern-mcp): un servidor stdio sin dependencias que da a Claude Desktop,
Cursor o cualquier cliente MCP un intérprete de código local. Apunta el cliente a él:{ "mcpServers": { "kern": { "command": "kern-mcp" } } }
Herramientas: run_code (python/bash/node), write_file, read_file, list_files. Cada llamada es una caja
nueva sin red; los archivos persisten entre llamadas en un workspace en disco. Comando de configuración, imagen y
las demás opciones: bindings/python/README.md.
API completa, Python y Node: bindings/python/README.md · bindings/node/README.md.
Un slice se declara una vez en ~/.config/kern/kern.toml y se adjunta por nombre, a una caja con sandbox o a
un proceso desnudo, con el mismo token.
Tres tipos: vcpu: (CPU y memoria), vdisk: (un disco scratch con límite de tamaño) y vgpio: (nodos
de dispositivo). Dos de ellos, y los anchors de los que se tallan:
[[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
Los perfiles componen: varios se adjuntan a una sola caja, y un flag explícito gana al valor del propio
perfil. Cada clave se escribe como su flag de CLI, así cpus es --cpus y memory es --memory. Un backend
que nombre un pool no declarado se rechaza al leer la config, no al ejecutar la caja.
docs/RESOURCES.md tiene el esquema campo por campo.
Un vdisk: es un tmpfs respaldado por RAM cuando kern se ejecuta sin root, diga lo que diga su backend, y una
imagen ext4-over-loop con cuota real cuando se ejecuta privilegiado. kern dice cuál de los dos tienes, por
perfil, en lugar de dejarte asumir, y el límite de tamaño se aplica en ambos casos.
vgpio: es granular por chip, no por línea. Pedir pins vincula todo el /dev/gpiochipN, y ese
dispositivo de caracteres expone todas las líneas de ese controlador. pins = [17] no restringe la caja a la
línea 17: el kernel no tiene una frontera de montaje por línea, así que la lista de pins es metadato cooperativo,
no una frontera. Nombrar un nodo de dispositivo, como hace i2c arriba, otorga ese nodo y nada más.
Intel i7-14700KF, Linux 7.0.0, el binario de release, un script que puedes ejecutar tú mismo:
python3 examples/benchmark.py. El tuyo diferirá con tu CPU, kernel y sistema de archivos.
Tres mil a la vez tardan ~2.2 s, y una caja viva cuesta ~0.3 MB de memoria.
Dos notas honestas. Nadie gana la latencia de un solo disparo por goleada: el suelo para unshare + exec
es de 1 a 2 ms, así que todo el nivel superior está dentro de su propio ruido, y bubblewrap es un lanzador sin
imágenes, capabilities ni ciclo de vida. La diferencia que importa es frente a los motores, dos órdenes de
magnitud por encima.
Método, desglose por fase, cifras de placas y cada advertencia: BENCHMARKS.md.
Namespaces, un pivot_root, 16 capabilities peligrosas eliminadas antes del exec, una allowlist seccomp
siempre activa por defecto (el filtro por defecto de moby menos las 35 syscalls de escape de kern, que
permanecen hard-killed; una syscall fuera del conjunto verificado devuelve ENOSYS, y la denylist más amplia es
la opción de exclusión vía KERN_SECCOMP=denylist), límites de cgroup v2 (--require-limits se niega a
arrancar salvo que se apliquen), y un /dev denegado por defecto. Cuando una frontera es cooperativa y no
aplicada por el kernel, SECURITY.md lo dice y nombra el bypass.
No tienes que fiarte: pentest/ contiene cuatro suites adversariales que comprueban esas fronteras contra el kernel, no contra el propio reporte de kern, y se ejecutan sin cuenta de registry ni red.
sh pentest/run-with-local-registry.sh ./target/release/kern pentest/pentest-ports.sh
Reporta una vulnerabilidad de forma privada vía GitHub Security Advisories o [email protected].
El núcleo está hecho. Todo lo anterior funciona hoy: 840 tests de Rust, 78 de Python y 61 de Node,
clippy-clean, cargo-deny-clean, en hardware real: Linux, WSL2, Raspberry Pi 5, Jetson Orin Nano,
Arduino UNO Q. v0.7.0 es la primera release publicada. La superficie de CLI y config aún puede cambiar,
siempre anunciado en CHANGELOG.md.
Las issues y pull requests son bienvenidas. CONTRIBUTING.md tiene el flujo de trabajo y las puertas; las contribuciones están cubiertas por el CLA.
Alex, @realexhub. Los commits vienen de @getkerndev, la identidad de commit del proyecto.
Los commits no están firmados; el TAG de release sí. Eso es lo que hay que verificar:
git verify-tag v0.7.0 contra la clave en provenance/, cuya huella está en
SECURITY.md.
Apache-2.0. Ver LICENSE y TRADEMARK.md.
kern compose <file> up toma un kern-compose.toml
(tablas [box.NAME], con los perfiles de recursos anteriores) o el docker-compose.yml que ya tienes,
leído tal cual. Un stack es un pod, con los servicios alcanzándose entre sí por nombre.ps, logs, exec, stats, inspect, wait, top (una TUI
en vivo), doctor, además de un SDK para Python y Node y un servidor MCP para agentes.| kern | Docker | Podman |
|---|
| Demonio | no | sí (dockerd + containerd) | no |
| Sin root | sí, siempre | opcional | sí |
| Arranque en frío, caja desnuda | ~2.3 ms | ~297 ms | ~293 ms |
| Arranque en frío, desde una imagen OCI | ~3.5 ms | ~297 ms | ~293 ms |
| Detener un servicio (init gestiona SIGTERM) | ~1.9 ms | ~310 ms | ~380 ms |
| Memoria residente, sin nada en ejecución | 0 | 154 a 160 MB | 0 |
| Huella | un binario de 1.52 MB | stack de demonio | instalación multi-binario |
| Imágenes OCI, pull / build / push | sí | sí | sí |
docker-compose.yml | sí, leído tal cual | sí | parcial |
| Overlay networks, Swarm, CRI | no | sí | parcial |
| GPU | en el roadmap | sí | sí |
| kern | bubblewrap | runc | podman | docker |
|---|
| Arranque en frío (caja desnuda) | ~2.3 ms | ~2.3 ms | ~18.6 ms | ~293 ms | ~297 ms |
| 200 cajas en paralelo | ~0.11 s | ~0.16 s | ~0.35 s | ~44.8 s | ~16.2 s |
| docs/INSTALL.md | instalar en Linux, WSL2 y placas ARM, desde el fuente |
| docs/DOCKER-COMPAT.md | qué de Docker funciona, qué no, y en qué difiere |
| docs/RESOURCES.md · docs/CONFIG.md · docs/STORAGE.md · docs/EGRESS.md | el modelo de dos verbos, el esquema kern.toml, volúmenes y egress |
| docs/THREAT_MODEL.md · SECURITY.md · OPEN_ITEMS.md | el modelo de amenazas (estructurado, luego por mecanismo), y las brechas conocidas |
| BENCHMARKS.md · EDGE.md | mediciones, y ejecución en una Pi, Jetson o UNO Q |
| examples/ · blog/ | noventa scripts ejecutables, y artículos más largos |
| bindings/python/README.md · bindings/node/README.md | el SDK kern-sandbox: embebe kern en Python o Node |