Browser-native Linux terminals powered by x86 emulation
Full Alpine Linux virtual machines running entirely in your browser via WebAssembly. No servers, no installs, no accounts. Inter-VM networking, file transfer, state snapshots, and offline support. Just open and type.
git clone https://github.com/Real-Fruit-Snacks/Shallows.git
cd Shallows
# Serve locally — any static file server works
python3 -m http.server 8000Open http://localhost:8000 in Chrome, Firefox, or Edge. Click New Terminal to boot an Alpine Linux VM.
No Node.js, no bundler, no package manager required. Just a static file server.
Note: If
SharedArrayBufferis unavailable, VMs still work but run slower. The included service worker injects the requiredCOOP/COEPheaders automatically.
Each terminal is a complete Alpine Linux x86 virtual machine running in WebAssembly. Not a shell emulator — a real kernel, real filesystem, real processes. BusyBox utilities and APK package manager included.
# Inside the VM — it's real Linux
apk add curl
uname -a
cat /etc/os-releaseAll terminals share a virtual ethernet switch. Unique MAC and IP addresses are auto-assigned on a 10.0.0.0/24 network.
# Terminal 1 (10.0.0.1)
nc -l -p 8080
# Terminal 2 (10.0.0.2)
echo "hello" | nc 10.0.0.1 8080
# Ping between VMs
ping 10.0.0.2Drag-and-drop files into a terminal to upload (written to /tmp/<filename>, max 2 MB). Run sendfile inside the VM to download files back to your browser.
# Upload: drag file onto terminal
# File appears at /tmp/<filename>
# Download: inside the VM
sendfile /path/to/fileFirst launch boots from ISO (~30s). After that, VM state (full memory + CPU) is snapshot-cached in IndexedDB for near-instant restore on subsequent terminals.
Tab view for focused work, grid view for monitoring multiple VMs. Up to 6 concurrent terminals with 128 MB RAM each. VM IDs are recycled from a pool to prevent unbounded growth.
No Docker, no SSH, no cloud accounts. v86 boots Alpine Linux directly in the browser tab using x86 emulation compiled to WebAssembly. All assets are self-contained — BIOS, WASM engine, Alpine ISO.
A service worker caches everything for offline use after the first load. No internet required after the initial visit. No external scripts, no CDN dependencies at runtime.
| Action | Shortcut | Button |
|---|---|---|
| Copy screen text | Ctrl+Shift+C |
Copy |
| Paste into VM | Ctrl+Shift+V |
Paste |
Shallows/
├── index.html # Entry point — topbar, tabs, viewport
├── sw.js # Service worker — offline + COOP/COEP
├── css/
│ └── style.css # Catppuccin Mocha theme
├── js/
│ ├── app.js # Event wiring, clipboard, shortcuts
│ ├── ui.js # Tab/grid management, drag-and-drop
│ ├── terminal-manager.js # VM lifecycle, IndexedDB snapshots
│ ├── virtual-network.js # VirtualSwitch + FakeWebSocket
│ ├── file-transfer.js # Serial-based upload/download
│ ├── libv86.js # v86 x86 emulator (WASM loader)
│ └── v86.wasm # v86 emulator core (WASM binary)
├── assets/
│ ├── bios/
│ │ ├── seabios.bin # SeaBIOS firmware
│ │ └── vgabios.bin # VGA BIOS firmware
│ └── images/
│ └── alpine-virt-3.20.3-x86.iso # Alpine Linux 3.20 (~47 MB)
└── docs/
├── index.html
└── assets/
├── logo-dark.svg
└── logo-light.svg
The v86 engine emulates an x86 CPU in WebAssembly. Each VM gets a VGA canvas for screen output, PS/2 keyboard for input, and a serial port (ttyS0) for file transfer. A VirtualSwitch broadcasts ethernet frames between FakeWebSocket adapters for inter-VM networking.
- No servers — all computation happens locally in your browser
- No accounts — no authentication, no tracking, no analytics
- No network calls — after initial load, Shallows works entirely offline
- Your data stays local — VM state is stored in IndexedDB on your machine only
- Service worker injects
Cross-Origin-Opener-PolicyandCross-Origin-Embedder-Policyheaders - All assets are self-contained and integrity-verifiable
- No external scripts, no CDN dependencies at runtime
| Capability | Chrome | Firefox | Edge | Safari |
|---|---|---|---|---|
| VM Boot | Full | Full | Full | Limited |
| SharedArrayBuffer | Full | Full | Full | Partial |
| Inter-VM Networking | Full | Full | Full | Full |
| File Transfer | Full | Full | Full | Full |
| Clipboard | Full | Full | Full | Limited |
| IndexedDB Snapshots | Full | Full | Full | Full |
| Service Worker | Full | Full | Full | Full |
| Offline Mode | Full | Full | Full | Full |
| Layer | Technology |
|---|---|
| Emulator | v86 0.5.319 (x86-to-WASM) |
| Guest OS | Alpine Linux 3.20 virt (x86 32-bit) |
| Networking | In-memory VirtualSwitch with FakeWebSocket |
| File Transfer | Serial port (ttyS0) with base64 marker protocol |
| State Cache | IndexedDB snapshot of full VM memory + CPU |
| Offline | Service Worker with cache-first strategy |
| Theme | Catppuccin Mocha |
| Build | None — vanilla HTML/CSS/JS |
| Problem | Solution |
|---|---|
| VM won't boot | Check console for WASM errors. Ensure js/v86.wasm and assets/ are accessible |
| Slow VM performance | SharedArrayBuffer may be unavailable. Ensure sw.js registered for COOP/COEP |
| Terminals can't ping | VMs auto-assigned unique MACs — check console for ip link set |
| File upload not working | Wait for VM to fully boot (loading overlay disappears) |
sendfile not found |
Command installed during auto-login. Reinstall manually if boot was interrupted |
| Clipboard buttons fail | Browser requires HTTPS or localhost for clipboard API |
MIT — Copyright 2026 Real-Fruit-Snacks
The repo includes .github/workflows/deploy.yml which deploys on every push to main. Enable Pages in your repo settings with Source: GitHub Actions. The Alpine ISO (47 MB) is committed directly — if your repo uses Git LFS, the workflow handles it automatically.