RV64GC Linux · in WebAssembly · zero servers

A complete Linux userland,
running in a browser tab.

NanoVM emulates an RV64GC RISC-V CPU in WebAssembly — enough Linux to run BusyBox, Node.js v25, and the full npm + TypeScript toolchain. No server. No install. Just a tab.

70
585 KB core wasm·~80 syscalls·Node v25·100% client-side
nano — RV64GC · node v25
~/proj $ uname -a
Linux nanovm 6.1.0-nano riscv64 GNU/Linux
~/proj $ echo "Hello from RISC-V" | busybox cat
Hello from RISC-V
~/proj $ node -e "console.log(process.arch, process.version)"
riscv64 v25.0.0
~/proj $ busybox ls /usr/bin
busybox node npm tsc eslint prettier
~/proj $ seq 5 | busybox sort -r
5
4
3
2
1
~/proj $ npm test
> nano@1.0.0 test
Results: 24 passed, 0 failed
~/proj $ 
↑ a static preview — the live terminal runs entirely in your browser. Type help, ls, or neofetch in the real demo.
What it is

A single ~585 KB WebAssembly module emulates a 64-bit RISC-V CPU with roughly eighty Linux syscalls — enough memory management, file I/O, sockets, and threading to load real ELF binaries and run them unmodified. No transpilation, no shims. The actual BusyBox and Node.js binaries, executing instruction by instruction.

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, 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

From ELF bytes to a running process.

01
Parse & load
A RISC-V ELF binary is loaded into guest memory with its argv, envp and auxv laid out.
02
Decode & dispatch
A dense exec() loop decodes each instruction; the dispatch compiles to a WASM br_table jump table.
03
Syscalls
~80 Linux syscalls are handled at syscall boundaries; file I/O crosses a shared-memory protocol, not per-instruction callbacks.
04
Threads & I/O
clone/futex threads, sockets, epoll and timerfds are scheduled cooperatively as the program runs.
See it run

Real binaries. Real output.

busybox — coreutils
~/proj $ echo "Hello from RISC-V" | busybox cat
Hello from RISC-V
~/proj $ busybox ls /usr/bin
busybox  node  npm  tsc  eslint  prettier
~/proj $ seq 5 | busybox sort -r
5
4
3
2
1
Architecture

A Bellard-style pipeline.

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 just five 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
5 WASM imports·~30 exports·0 dependencies (except libm)·shared-memory FS protocol
585 KB
core wasm (no binaries)
68 MB
with BusyBox + Node
~80
Linux syscalls
5 / 30
imports / exports
24
tests passing
Where it fits

Lighter than a VM. Closer than a sandbox.

NanoVM
Cloud sandbox
Full-system emu
Runs in the browser
yes
no (remote)
yes
Runs Node.js
yes
yes
heavy
Server required
no
yes
no
Core download
585 KB
several MB
Scope
userland
full host
full OS
A userland emulator — it runs Linux programs, not a full kernel + devices.
The headline use case

A WebContainers alternative that runs real Node.

Run the unmodified Node.js v25 binary — plus npm, TypeScript, ESLint and Prettier — entirely client-side. No server, no install, and it’s open source.

Run Node.js 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.
Embed it with the SDK

Drop a Linux userland into your app.

@userland-run/nano-sdk is a typed, ESM, zero-dependency package: boot the VM, write files, run real binaries, host servers, and sandbox plugins. Code mode, a terminal engine, serve mode, scripting and a Web Worker transport — all from one import.

javascript
import { createNano, nanoImage } from "@userland-run/nano-sdk";

const nano = await createNano({
  image: nanoImage({ baseUrl: "/nano/", withNode: true }),
});

nano.fs.writeFile("/app/data.txt", "3\n1\n2\n");
const { stdout } = await nano.shExec("sort -rn /app/data.txt | head -1");
console.log(stdout); // "3"
Quick start

Clone, build, run.

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

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

# browser IDE: wasm + bundled binaries + vite
make demo