snowglobe

Running outside a browser

The same snapshot runs on your terminal, in a container or natively, from a folder, a tarball or a deployed site's URL.

Sessions and instances

snowglobe run dist/elixir                    # a session; ctrl-] quits
printf 'Enum.sum(1..100)\n' | snowglobe run dist/elixir
snowglobe run dist/elixir --name pg --detach
snowglobe exec pg 'sql "SELECT 1"'           # prints only what the command printed
snowglobe attach pg                          # join it; ctrl-] detaches
snowglobe ps
snowglobe stop pg

snowglobe run uses a container by default (--runtime docker: unprivileged, read-only, memory-capped). --runtime native (or SNOWGLOBE_RUNTIME=native) runs snowglobe-vm itself, with no Docker. Piped input is typed in one line at a time, each waiting until the app goes quiet. Each instance starts fresh from the snapshot.

Instances share their memory

Native instances of one site don't each hold a copy of its snapshot. The first one writes the snapshot's memory to an image in the cache, and every instance after it maps that image copy-on-write: the pages no instance writes are kept once for all of them, and a page an instance writes becomes its own. It restores in a few milliseconds instead of decompressing the snapshot first.

Ten instances of the Python runEach, beforeEach, now
Memory on Linux (PSS)111 MB46 MB
Memory on macOS (physical footprint)175 MB96 MB
Restore60–74 ms4–6 ms

The image lives in ram/ under the cache (SNOWGLOBE_CACHE, by default ~/.cache/snowglobe), one per snapshot, named by its hash. It takes about as much disk as the snapshot's memory in use (71 MB for the Python run), and images unused for two weeks go when a new one is made. Machines forked from a template, by --fork or the daemon, already shared the template's memory and still do; their template server maps the image too. How the image is kept from changing under a machine is under Isolation; SNOWGLOBE_RAM_IMAGE=off restores into memory of the instance's own.

Sites from a URL

A site doesn't have to be local. snowglobe run takes a deployed site's URL, which it mirrors into a cache, or a tarball made with snowglobe pack, local or by URL. Downloads happen on the host and are revalidated, so the second run starts from the cache and the sandbox stays offline.

snowglobe run https://filipecabaco.github.io/snowglobe/python/
snowglobe pack dist/elixir -o elixir.tar.gz
snowglobe run https://cdn.example.com/elixir.tar.gz

Serving from the guest

A site built with --network fetch can serve: -p HOST:GUEST forwards a port on your machine to one in the guest. Host ports bind to 127.0.0.1 unless you give an address (-p 0.0.0.0:8080:4000).

One executable

snowglobe bundle makes a site a single executable: snowglobe-vm with the site appended. It needs no Docker, hypervisor or KVM, so it runs on a laptop, in CI or inside someone else's container. Build with --sandbox for this: the guest keeps its file cache in the snapshot, and exercises run before it.

snowglobe build examples/python --sandbox --out dist/python
snowglobe bundle dist/python -o python        # Linux; --target darwin for Apple silicon
echo 'sum(i*i for i in range(10))' | ./python

A bundle whose app is a shell also runs one-shot jobs: --workspace DIR is written into a fresh machine, --cmd runs there, and its output, exit status and out/ come back. --serve SOCKET --jobs-root DIR restores the snapshot once and answers each job with a fork of it, sharing the restored machine copy-on-write; --server SOCKET is its client.

./toolkit --workspace job --cmd 'sh main.sh' --env 'N=42'
./toolkit --serve /run/toolkit.sock --jobs-root /var/lib/jobs &
./toolkit --server /run/toolkit.sock --workspace /var/lib/jobs/1 --cmd 'sh main.sh'