Run your app as its own virtual machine.
jerboa turns a Linux program into a bootable image with its own kernel, and runs it as a Firecracker or QEMU VM.
curl -fsSL 'https://jerboa.dev/install.sh' | sudo bash - Source to responsebuild, boot and first HTTP answer: 859 ms
- < 1 s
- HTTP requests per secondagainst Docker on macOS, Go server
- 1.9×
- Random disk readsagainst Docker on macOS, fio 4k
- 6.3×
A recorded session
Build a Go web server, start it as a VM and request a page. Recorded on an Apple M5; the transcript is unedited.
$ jerboa build . --name web Building project... ✓ Build complete Assembling image on daemon... ✓ web:latest · 10.4MB · built in 659ms$ jerboa run -d -p 8080:8080 --name web webe6cf1bf78d40Started in 27ms$ curl localhost:8080hello from a linux/arm64 unikernel$ jerboa psID NAME STATE HEALTH IMAGEe6cf1bf78d40 web running sha256:8e36de256043…$ jerboa stats webState runningCPU 0.0%Memory 59.4 MiBDisk 3.4 MiB
- Image built659 ms
- VM launched686 ms
- First response859 ms
One kernel per app
A container shares the host’s kernel with every other container. A jerboa VM carries its own Nanos kernel behind the hypervisor, with no shell, no init and no other process.
Commands
The workflow follows containers: build, run, publish, network, mount. Every command below comes from the documentation.
# Go, Node.js, Python and Rust projects are detected jerboa build . --name web # Drop the boot code and the second kernel copy jerboa build . --name web --layout compact # See which programs and packages fill the image jerboa build . --name web --size-report
- Language drivers compile the project into the image
lang = "raw"packages any prebuilt Linux binary- A compact Go web server image weighs 7.5 MB
# redis starts through a shell script, so point --file # at the real binary: a unikernel runs one program, no shell jerboa pkg from-docker redis:7.2 redis:7.2 \ --file /usr/local/bin/redis-server jerboa build . --name redis
- Takes a binary and the libraries it links from an image
- Files land at the paths the program expects
- Works on scratch and distroless images
jerboa network create app jerboa run myapp:latest --network app -p 8080:80 --name web # Bind a published port to localhost only jerboa run myapp:latest --network app -p 127.0.0.1:8080:80
- Publishes TCP and UDP ports
- Kernel NAT for local clients on Linux
jerboad --port-publish nat-lanextends it to the LAN
jerboa network create app jerboa run mysql:latest --network app --name db -p 3306:3306 jerboa run myapp:latest --network app --name web -p 8080:8080 -e DB_HOST=db # inside `web`, the hostname `db` resolves to the db VM's IP
- Bridge networks with static or allocated addresses
- The daemon answers DNS from live VM state
- Peers connect by name, not by IP
jerboa volume create data --size 1G jerboa run myapp:latest -v data:/var/data # Seed a volume from a package at creation jerboa volume create pgdata --size 1G --seed-pkg eyberg/postgresql:11.3.0 --src /db jerboa run postgresql -v pgdata:/db --network pgnet -p 5432:5432
- Persistent volumes, kept across restarts
- Owned by the daemon, mounted into the VM
- Seeded from packages, e.g. a ready PostgreSQL data directory
version: "1" services: api: image: api:latest memory: 256M networks: [app] ports: - "8080:8080" health_check: "http:8080:/healthz" restart: "on-failure:5" networks: app: driver: bridge subnet: 10.100.0.0/24 # jerboa compose up stack.yaml
- Services, networks and volumes in one file
- Health checks and restart policies
- Services reach each other by name
jerboa logs <vm-id> -f jerboa stats web jerboa inspect web
- Serial console output, followed live
- CPU, memory, network and disk per VM
logsexplains fork and exec failures when it sees them
jerboa snapshot create <vm> <name> # pause, capture, resume jerboa snapshot restore <vm> <name> # stopped VM -> running, in place jerboa snapshot ls
- Pause a VM and resume it in place
- Restore targets the VM the snapshot came from
- macOS with Firecracker
Against Docker
Same binaries on both sides, runs interleaved, medians of at least three. jerboa is ahead at start-up, random disk reads and, on macOS, request-heavy apps. Docker is ahead at sequential writes, at sending from a VM to the host on macOS and, on Linux, for clients on the LAN.
macOS Apple M5, 10 cores, 16 GB, macOS 26
Docker Desktop runs all containers inside one Linux VM; jerboa boots one VM per app on Hypervisor.framework.
Linux Xeon E5-2620 (2012), nested KVM guest with 2 vCPU and 4 GB
This host is itself a VM without APICv, so every guest exit costs 50–120 µs instead of 1–2 µs. That penalizes jerboa’s VMs and not Docker’s containers.
Trade-offs
A unikernel trades flexibility for isolation and size. These are the limits you hit first.
- One process per VM
- Nanos has no fork, no exec and no shell. Entry-point scripts must point at the real binary; tools that spawn helpers, like PostgreSQL’s
initdb, run at build time. - No shell into a running VM
- There is nothing to exec into.
jerboa execsends a signal; debugging goes through logs, stats and the serial console. - macOS is a development preview
- It needs Apple Silicon and macOS 26 or newer, and the app is not yet signed by Apple.
- Windows runs through WSL2
- The daemon lives in a dedicated WSL2 distro. There is no native Windows virtualization.
Install
One installer ships the CLI and the daemon. Then build any Go, Node.js, Python or Rust project.
curl -fsSL 'https://jerboa.dev/install.sh' | sudo bash