snowglobe

Real programs, sandboxed in your browser.

Each run below is a whole Linux machine, packaged from a Dockerfile and emulated in WebAssembly right in this tab. No server runs it, its only way out is your browser's own requests, and closing it throws the machine away.

Run one here to type into it, or open it in a window of its own. It is a 64-bit ARM CPU in emulation, so it is slower than native; every number and transcript below was measured and recorded when this site was built.

Compare the runs

The snapshot is what a run downloads before the machine resumes. Preloaded files follow in the background: everything the run read while booting, or while its commands were exercised. The rest is fetched only if you touch it.

RunSnapshotReady at buildPreloadedOn demandFiles

Make your own

Every run on this page came out of one command. snowglobe is a single Go binary; Docker is all else it needs, and the result is a folder of static files you can host anywhere.

Install

$ go install github.com/filipecabaco/snowglobe/cmd/snowglobe@latest

Describe

Any arm64 Alpine image works. Its CMD, ENV and WORKDIR are what the visitor's terminal gets. Labels say when the program is ready, and which commands to exercise so their files are preloaded. The Python run's Dockerfile, exercise list shortened:

# examples/python/Dockerfile
FROM alpine:3.24.2
RUN apk add --no-cache python3 && \
    python3 -m venv /opt/venv && \
    /opt/venv/bin/pip install --no-cache-dir rich==15.0.0
COPY startup.py /root/startup.py
ENV LANG=C.UTF-8 PATH=/opt/venv/bin:$PATH \
    PYTHONSTARTUP=/root/startup.py
CMD ["python3"]
LABEL snowglobe.title="Python + Rich in the browser" \
      snowglobe.ready=">>> " \
      snowglobe.exercise='["inspect(str.split)", "1 / 0"]'

Build

It adds a kernel and a 9p boot layer, converts the image, boots it headless until the ready text appears, snapshots it, then types the exercises and measures what they read. An excerpt of the real log from this site's build:

$ snowglobe build examples/python --out dist/python

==> Resolve source image
    command: 'python3'
    ready:   ">>> "
    memory:  512 MB
    network: none
==> Add boot layer
==> Convert to a 9p filesystem
    5600 entries, 107 MB uncompressed, 4437 unique files
==> Boot and snapshot
App ready on hvc0 after 19.966s
Saved /out/system/state.bin.zst (13 MB)
Exercising: t = Table("language", "year")… (0 files, 3.3s)
Exercising: Syntax("def fib(n):…", "python") (4 files, 3.5s)
==> Write warm pack
    warm.pack: 415 files, 8.7 MB
    read while booting       409 files   8.5 MB
    first read by: Syntax…     3 files   0.0 MB
    never read              4018 files  36.9 MB

dist/python is ready to deploy.

Ship

Preview it locally, then put the folder on any static host. Make it an app people can install and run offline, and let several runs on one host share one blob store. This site is GitHub Pages, built by a workflow running the same commands; every run on it is installable, and each one shares to X as a post that boots it.

# preview at http://localhost:8000
$ snowglobe serve dist/python
# installable, and offline once it has run
$ snowglobe pwa dist/python
# several runs on one host: share one blob store
$ snowglobe pool dist

Run

The same site runs without a browser, in a sandboxed container: a session in your terminal, lines piped in as commands, or a long-lived instance that scripts and agents drive with exec. It runs from a folder, from a site's URL (try snowglobe run https://filipecabaco.github.io/snowglobe/python/), or from one tarball on a CDN.

# a session in your terminal; ctrl-] quits
$ snowglobe run dist/python
# from where it's hosted: a site's URL, or one tarball
$ snowglobe pack dist/python -o python.tar.gz
$ snowglobe run https://cdn.example.com/python.tar.gz
# one instance, driven command by command
$ snowglobe run dist/python --name py --detach
$ snowglobe exec py 'sum(range(10))'
45

Commands

CommandDoes
build <dir | image>package a Docker image (a directory with a Dockerfile, or an image) into a static site; the flags are below
serve [dir] [--port N]preview a built site at localhost, port 8000 by default
pwa <site>make it an installable app that works offline once it has run; --name and --short-name override the title
pool <dir>let every site under a directory share one blob store, for hosting several together
pack <site> [-o file]write a site as one tarball (.tar.gz or .tar.zst) to publish on a CDN
run [site | url]run a site without a browser, in a sandboxed container: a session in a terminal, or piped lines typed in as commands. Takes a folder, a tarball, or a URL to either; --detach --name N keeps it running, and -p 8000:8000 forwards a port into a guest built with a network
exec <name> <command>type a command into a running instance and print only its output
attach <name>join a running instance's session; ctrl-] detaches and leaves it running
pslist running instances
stop <name>throw a running instance away

Build flags

FlagLabelSets
--ready TEXTsnowglobe.readyterminal output that means the program is ready to snapshot
--exercise CMDsnowglobe.exercisecommands typed after the snapshot; their files get preloaded
--cmd CMDrun something other than the image's own CMD
--memory MBsnowglobe.memoryguest RAM, 512 by default
--title TEXTsnowglobe.titlethe page title
--warm REGEXsnowglobe.warmextra guest paths to preload
--network fetchsnowglobe.networkgive the guest a network: HTTP(S) and WebSockets leave as the visitor's own browser requests, so CORS applies; none by default
--out DIRwhere the site is written, dist by default