kernel + peer runners · WebAssembly · zero servers

A multi-runner Linux platform,
running in a browser tab.

One kernel routes each program to the right engine — a RISC-V VM, Node on the host JS engine, WASI commands, or sandboxed JS — running real BusyBox, Node.js v25 and the npm + TypeScript toolchain. No server. No install. Just a tab.

70
4 runners·2.3 MB slim wasm·Node v25·100% client-side
What it is

A shared kernel — an in-memory filesystem, a process router, a bus, capabilities and services — with four peer runners on top: a browser-based RISC-V VM that runs real ELF binaries unmodified, Node.js on the host JS engine, a wasm32-wasip1 tier, and a sandboxed JS engine. Each program runs on the right one. No transpilation, no shims — the actual BusyBox and Node.js binaries, plus wasm apps and scripts, over one operating-system layer.

Four runners, one kernel

The right engine for each program.

A runner imports only from the kernel — never another runner. Cross-tier work goes through the router, the bus and the shared VFS, so a wasm tool, a Node server and a sandboxed script all see the same filesystem.

riscv · slow
Runs RV64 ELF (BusyBox, Node.js) on emulated CPU · Rust→wasm. fidelity oracle.
node · fast
Runs Node.js on the host JS engine. trusted.
wasm · fast
Runs wasm32-wasip1 commands on the host wasm engine. capability-scoped.
boa · medium
Runs untrusted JavaScript on Boa interpreter · Rust→wasm. sandboxed.
BusyBox
echo, cat, ls, grep, sort, head, tail and the rest of the coreutils — the real multi-call binary, not a shim.
Node.js v25
The real v25.4.0 runtime: require, fs, path, crypto, http, streams, Buffer, EventEmitter and async/await.
npm + TypeScript
npm 11.18.0, the TypeScript compiler, ESLint and Prettier — the actual binaries, running unmodified inside the VM.
Host-fetch networking
Outbound HTTP/HTTPS is brokered through the host’s fetch() — even npm install works, CORS permitting.
App catalog
Install extra tools — fd, ripgrep and more — on demand from a signed, content-addressed catalog.
Sockets & epoll
Real socket, epoll and timerfd syscalls — enough to run an HTTP server inside the VM.
Threading
clone / futex-based cooperative multithreading, context-switched at syscall boundaries.
In-memory POSIX FS
A full POSIX filesystem in memory, with brk / mmap memory management underneath.
ELF loader
Loads real RISC-V ELF binaries — segments, argv, envp and auxv, exactly as Linux would.
HTTP preview
A service-worker bridge renders servers running inside the VM in a live preview iframe.
How it works

Route it, run it, share one kernel.

01
Route
The kernel picks a runner for each program — the RISC-V VM, the host Node engine, the wasm tier, or the Boa sandbox — by ABI and policy.
02
Run
The chosen engine executes it: riscv decodes RV64 through a dense exec() br_table; node/wasm/boa run on native host engines.
03
Share
One kernel underneath — a shared in-memory VFS, a process table, a router and a bus. Runners talk only to the kernel, so tiers compose.
04
Bridge
~80 Linux syscalls, host-brokered fetch, sockets/epoll, and a service-worker preview bridge — the same host boundary for every tier.
See it run

Real binaries. Real output.

busybox — riscv runner
~/proj $ echo "Hello from RISC-V" | busybox cat
Hello from RISC-V
~/proj $ busybox ls /bin
busybox  sh  ls  cat  echo  grep  sort  seq  wc
~/proj $ seq 5 | busybox sort -r
5
4
3
2
1
Inside the RISC-V runner

A Bellard-style pipeline.

The riscv runner — the fidelity oracle of the four tiers — is a monolithic exec() function whose dense dispatch compiles to a WASM br_table. #![no_std] Rust, fat-LTO'd into one function, talking to the host over a handful of imports.

INPUT
RISC-V ELF
segments · argv/envp
DECODE
Instruction decode
field extraction
CORE
exec() loop
br_table · #![no_std]
KERNEL
Syscall dispatch
~80 syscalls
HOST
MemFS / host
shared-memory FS
6 WASM imports·~30 exports·1 dependency (vte, no_std)·shared-memory FS protocol
2.3 MB
slim wasm (no binaries)
4
runners · one kernel
~80
Linux syscalls
6 / 30
imports / exports
55
tests passing
Where it fits

Lighter than a VM. Closer than a sandbox.

NanoVM
WebContainers
Sandpack
BrowserPod
container2wasm
Unmodified upstream binaries
Yes
No
No
Recompiled
Yes
Server / cloud required
None
Commercial API
Bundler host
API key + metered
None
Core download
2.3 MB
Several MB
MBs
Several MB
MBs+
Fully open source
Yes
No
Partial
No
Yes
Scope
Userland
Node env
JS sandbox
Linux-ish
Full OS
A userland VM — it runs Linux programs, not a full kernel + devices.
See the runners live

Real Node, wasm apps and sandboxed JS — in this tab.

The Node-in-the-browser page runs live demos on each tier: the unmodified Node.js v25 binary and toolchain, an image CLI on the wasm runner, a capability-scoped Boa script, and a cross-tier handoff between the host engine and the RISC-V VM — all client-side, open source.

Run it in the browser →
In-browser IDEs
Compile and run TypeScript or Node projects client-side — no backend build servers to operate.
Secure sandboxes
Execute untrusted code inside a WASM boundary, with no host filesystem or network access.
Teaching & playgrounds
A real Linux shell for courses and docs — reproducible, shareable, and impossible to break.
Build your own

From watching it run to embedding one.

Two on-ramps, one model. Prototype in the live terminal above — npm install a package, write a server, watch it render. Then lift it into code with @userland-run/nano-sdk: a typed, ESM, zero-dependency package. Drop in <nano-terminal> for the whole UI, or call createHeadless for the same features without it — the full terminal, or just its capabilities, in your own page in minutes.

javascript
// Display the whole terminal — files, editor, live preview, ⌘K palette — as one
// web component. Shadow DOM keeps its styles fully isolated from your page.
import { defineNanoTerminal } from "@userland-run/nano-sdk/terminal";
defineNanoTerminal();
// <nano-terminal wasm-url="/nano/nano.wasm" style="height:560px"></nano-terminal>

// …or take the same features headlessly — no UI, just the capabilities:
import { createHeadless } from "@userland-run/nano-sdk";
const s = await createHeadless({ wasmUrl: "/nano/nano.wasm" });
s.fs.writeFile("/work/server.js", code);     // files
await s.installApp("ripgrep");                // catalog
await s.run("node /work/index.js");           // run
const srv = await s.serve({ node: ["/work/server.js"] }, { port: 3000 });
print(srv.url()); // routed through the service worker
Quick start

Clone, build, run.

bash
# clone & build the slim ~2.3MB wasm module
git clone https://github.com/userland-run/nano
cd nano
make build

# run the suite — 55 passed, 0 failed
make test

# serve the build locally on :8080
make serve