snowglobe

Docs

snowglobe packages a Docker image into a folder of static files that runs it in a browser tab: real Linux on an emulated 64-bit ARM CPU, restored from a snapshot taken once your app was ready. The same site also runs headlessly, as one executable, and as a sandbox an agent drives.

Getting started

Build a site from a Dockerfile, preview it, then put the folder on any static host.

What you need

  • Docker, or nothing. With Docker, snowglobe builds your Dockerfile and boots it in a build container. Without Docker, --builder native builds from an image in a registry or a saved archive, and snowglobe run --runtime native, bundles and snowglobe mcp run sites with no Docker either.
  • An arm64 Alpine image: FROM alpine (multi-arch; snowglobe builds it for arm64) or FROM arm64v8/alpine. On an x86 host, Docker needs QEMU's binfmt handlers to build arm64 images. Docker Desktop has them; on Linux run docker run --privileged --rm tonistiigi/binfmt --install arm64 once.

Install

Download a binary for Linux or macOS (amd64 or arm64) from the releases, or install it with Go 1.27 or later:

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

The first build fetches the tools image (the machine that boots and snapshots your image) from ghcr.io/filipecabaco/snowglobe-tools. A version that hasn't been released compiles it locally instead, once, in a few minutes. Without Docker, the first native build or run fetches this release's snowglobe-vm and its prebuilt kernel into your cache.

Your first site

Any directory with a Dockerfile, or any image reference, builds into dist/:

snowglobe build ./my-app          # a directory with a Dockerfile, or an image reference
snowglobe serve dist              # preview at http://localhost:8000

Whatever the image runs (its ENTRYPOINT and CMD, with its ENV and WORKDIR) appears in a terminal on the page. dist/ is plain static files: upload it to GitHub Pages, a CDN or any web server. There is no snowglobe server.

Everything runs on an emulated 64-bit ARM CPU, translated to WebAssembly as it runs, so expect it to be slower than native. Cold loads take seconds; commands take tens of milliseconds to a few seconds.

Building a site

A Dockerfile describes the app; labels in it, or flags, describe how snowglobe should snapshot it.

Describe it in the Dockerfile

The Python demo is this Dockerfile. Labels let an image describe itself, and flags override them:

FROM alpine:3.24.2
RUN apk add --no-cache python3
CMD ["python3"]
LABEL snowglobe.title="Python in the browser" \
      snowglobe.ready=">>> " \
      snowglobe.exercise='["import json, re, collections", "import asyncio; asyncio.run(asyncio.sleep(0))"]'

When the snapshot is taken

snowglobe boots the image until the app is ready, then saves the whole machine. Visitors restore that snapshot rather than booting Linux. Tell it what "ready" looks like with --ready (or snowglobe.ready): text the app prints, usually its prompt. Without it, the app counts as ready once its terminal has been quiet for 5 seconds.

--cmd swaps the program without touching the image, and drops the image's ready and exercise labels, which belonged to its own command.

Exercises and the warm pack

The guest fetches its files on demand. To hide that, every site preloads a warm.pack in the background, filled by measuring rather than guessing:

  • Boot reads: every file the guest read while booting.
  • Exercise reads: after the snapshot, snowglobe types each --exercise command into the app and records what it reads: the files a visitor's first commands would otherwise wait for. A command is done once the app prints its prompt again and goes quiet.
  • Pattern: anything matching --warm REGEX, for files no exercise touches.

Each build prints a breakdown and writes warm-report.json. An app used over the network rather than its console can be warmed from real traffic instead:

snowglobe run dist --name api --detach -p 5000:5000 --record-reads api-reads.json
npm test                                    # anything that talks to 127.0.0.1:5000
snowglobe stop api && snowglobe warm dist api-reads.json

CPUs and memory

--memory MB sets the guest's RAM (512 by default). --cpus N (1 to 8) gives it more CPUs, each in its own Web Worker on shared memory, which helps programs that use them. Browsers only allow that on a cross-origin isolated page, which a static host can't declare, so such a site ships a small service worker that reloads the page once on the first visit, and it can't run inside another page's frame. A build boots on at least 4 CPUs (--build-cpus) and takes the extra ones offline before the snapshot.

Services in the image

The guest boots with BusyBox's init and no service manager, so the app is ready seconds after the kernel starts. Services an image enables for OpenRC do not start: start them from the command, as the Postgres and Elixir examples' start scripts do. Slow first-time setup (an initdb, migrations) belongs in a Dockerfile RUN, where it runs at native speed instead of in emulation.

Building without Docker

--builder native (or SNOWGLOBE_BUILDER=native) builds from an image reference, pulled from its registry, or an image archive (docker save, or an OCI layout from buildah, kaniko or anything else). Nothing runs at build time either way: the image's layers are flattened, the prebuilt kernel and boot files are added on top, and snowglobe-vm boots and snapshots it on your machine.

snowglobe build python:3.13-alpine --builder native --ready '>>> '
docker save my-app:latest -o my-app.tar         # on a machine with a builder
snowglobe build my-app.tar --builder native     # anywhere else

Hosting several sites, and installing them

snowglobe pool <dir> lets every site under a directory share one blob store. Blobs are named by content, so sites built on the same base share many, and a visitor's second site reuses what their browser cached.

snowglobe pwa <site> makes a site an installable app that keeps working offline once it has run: a manifest, icons and a service worker caching the page, the machine and every file the run fetched. Run it after pool.

Networking

A guest has no network unless the site asks for one, and then its only way out is the visitor's own browser.

HTTP, HTTPS and WebSockets

With --network fetch (or snowglobe.network="fetch"), the guest gets a network card and DHCP, and every request it makes leaves as the page's own browser request. Its connections to ports 80 and 443 end inside the machine:

  • HTTP requests become a fetch().
  • HTTPS is terminated in the machine with a certificate signed by a CA made fresh for each build, which the guest trusts, and replayed with fetch("https://…"). Nothing else should trust that CA: its key ships with the site.
  • WebSockets open a browser WebSocket to the same URL.

The browser's rules still apply. HTTP(S) only reaches servers that allow CORS (others answer with a 502 that says so), UDP goes nowhere, and other TCP only through a tunnel.

Other TCP: tunnels

A page can't open raw TCP, so a connection to a database or any port but 80 and 443 is refused unless the site names it as a tunnel. --tunnel HOST:PORT=URL carries every connection to HOST:PORT over a WebSocket to URL, as binary messages: what websockify, websocat and Neon's wsproxy speak.

websocat --binary ws-l:0.0.0.0:6543 tcp:127.0.0.1:5432       # next to Postgres
snowglobe build ./my-app --network fetch --tunnel db.internal:5432=wss://db-proxy.example.com/

Credentials

Whatever the guest knows ships with the site: a password in its environment is in the snapshot, for every visitor.

Instead, put {token} in the tunnel URL and name a token endpoint with --tunnel-token URL. The page asks it for a fresh token on every connection (natively, once per boot), and a gateway behind the tunnel checks the token and holds the real credentials. A token only replaces the password: what it opens is whatever the gateway lets any visitor do.

Builds and snowglobe run refuse loopback and private addresses, so a guest can't reach the host's own services. --allow-private lets them, for a tunnel endpoint on your own machine while developing.

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'

For agents and programs

A native instance takes jobs on the guest's root shell, whatever its app is, and a run can be bounded, stopped from outside and recorded. For many machines, the daemon forks them from templates in milliseconds, scales services to zero and applies whole fleets from a file.

Commands and files: sh and cp

Pass the command to snowglobe sh as one quoted argument; its flags would otherwise be read as snowglobe's own.

snowglobe run dist/python --name box --detach --runtime native
snowglobe sh box 'python3 -c "print(6*7)"'      # its output, and its exit status as ours
snowglobe sh box --json 'ls /nope'
snowglobe cp ./project box:/root/project        # files and directories, either way
snowglobe cp box:/root/project/out ./out

Each command runs with the app's environment in a fresh working directory; files elsewhere persist for the instance's life. Out of time (300 s, or --timeout S), the command and everything it started are killed (exit 124). What the guest sends back through cp can't write outside the destination.

A fresh machine per command: --fork

--fork makes a detached native instance a template. It restores once, and every snowglobe sh on it runs in a copy of that machine, forked in milliseconds and sharing its memory. Nothing one command does is seen by the next, so sh is all a fork instance takes: cp, exec, attach and mcp need a machine that lasts.

snowglobe run dist/python --name tpl --detach --runtime native --fork
snowglobe sh tpl 'python3 untrusted.py'         # many at once, each on its own copy
snowglobe stop tpl

Templates and machines: the daemon

snowglobe daemon keeps templates (a site restored once) and the machines forked from them. snowglobe up forks a machine from a template in tens of milliseconds. The machine shares the template's memory, lasts until it is stopped, keeps what it writes, and has its ports forwarded by the daemon. sh, cp, exec, attach, ps and stop reach the daemon's machines as they reach instances.

snowglobe daemon &
snowglobe template add dist/python --name py     # restored once, its JIT code compiled once
snowglobe up py --name web -p 8080:8000 --label app=web
snowglobe up py --name worker                    # another machine, its own state
snowglobe sh web 'python3 -m http.server 8000 &'
snowglobe ps
snowglobe stop worker
snowglobe template rm py                         # and its machines

The API is HTTP/JSON on a Unix socket in the user cache (curl --unix-socket works): /v1/templates, /v1/machines, /v1/machines/{name}/jobs and /v1/templates/{name}/jobs (a job on a fresh fork). --listen ADDR --token-file F also serves it over TCP, to requests bearing the token, and SNOWGLOBE_DAEMON=http://HOST:PORT with SNOWGLOBE_DAEMON_TOKEN points the CLI at it. A daemon started again takes back the templates and machines that are still running.

Limits and lifecycle

snowglobe daemon --memory 8G caps what its template servers and machines take together. A machine that doesn't fit waits up to --wait (30 s) for room, then is refused with the reason. Linux measures each process's proportional share; macOS its resident memory, which counts pages shared with the template in full, so the cap is reached sooner. No cap unless asked. up --ttl 1h stops a machine after that long, whatever it is doing. template add --idle 30m stops a template's server after that long without a machine; the next up (or job) restores it first, in about a second.

snowglobe daemon --memory 8G --wait 30s &
snowglobe template add dist/python --name py --idle 30m
snowglobe up py --ttl 1h

Events and top

snowglobe top shows the daemon live: memory against its budget, templates (awake or asleep), services with their replicas and open connections, machines with what each takes and when it stops, and the latest events. snowglobe events streams those events (machines up and stopped, replicas started and stopped, templates asleep and awake, snapshots, admissions refused), the last 200 first; --json for programs. The API is /v1/events (server-sent events, ?after=SEQ to resume) and /v1/stats.

snowglobe top
snowglobe events --json | jq 'select(.kind == "admission.refused")'

Fleets: snowglobe apply

A snowglobe.toml says what the daemon should have, and snowglobe apply gets it there: templates (a built site, a URL, or a Dockerfile directory built first), snapshots (a machine of a template set up by commands, saved once a readiness command succeeds), templates of those snapshots, services and machines. snowglobe diff says what apply would do; snowglobe down removes all of it. An entry that changed is replaced, with what is made from it; one gone from the file is removed; names something else uses are refused. One file goes from source to running services (examples/fleet/snowglobe.toml):

[templates.py]
build = "../python"                # built first, for running natively
network = "fetch"

[snapshots.serving]
template = "py"
run = ["cd /srv && python3 -m http.server 8000 >/dev/null 2>&1 &"]
ready = "wget -q -O /dev/null http://127.0.0.1:8000/"

[templates.serving]
snapshot = "serving"

[services.web]
template = "serving"
ports = ["8080:8000"]
min = 0                            # scaled to zero, woken by a request
idle = "5m"

[machines.scratch]
template = "py"
ttl = "24h"

Snapshots of running machines

snowglobe snapshot save saves a machine whole: its memory, its running processes and the files it wrote. Machines start from it again with up --snapshot, or it becomes a template of its own with template add --snapshot. Set a machine up once (dependencies installed, a server started, data loaded), then fork as many as you want, already set up.

snowglobe up py --name base
snowglobe sh base 'pip install httpx && python3 -m http.server 8000 &'
snowglobe snapshot save base --name ready        # tens of milliseconds
snowglobe template add --snapshot ready --name py-ready
snowglobe up py-ready -p 8000                     # installed, its server already listening
snowglobe snapshot ls
snowglobe snapshot rm ready

Services and scale to zero

A service puts a template's machines behind host ports that the daemon holds. Each connection (or snowglobe sh <service>) goes to one of its replicas, started on demand. With --min 0 it scales to zero: replicas with no connection or job for --idle stop, and the next request forks one again. Made of a snapshot whose server was already listening, a service at zero answers its first request in about 20 ms. --state keep saves the replica before it stops, and the next one carries on from that save (one replica); --state discard, the default, starts every replica from the template.

snowglobe service add web --template py-ready -p 8080:8000 --min 0 --max 3 --idle 5m --concurrency 10
snowglobe service add db --template pg --state keep --idle 10m
snowglobe service ls                              # replicas running now, of each
snowglobe service rm web

A replica takes --concurrency connections before another is started, up to --max; under --min, replicas that stop are replaced. The API is /v1/services and /v1/services/{name}/jobs.

MCP

snowglobe mcp <site> serves a site to an agent as an MCP server over stdio: an isolated Linux machine with run_command, write_file and read_file tools. It starts a native instance and stops it when the agent goes (--keep leaves it).

claude mcp add sandbox -- snowglobe mcp https://filipecabaco.github.io/snowglobe/python/

Limits, stopping and logs

FlagWhat it doesExit
--max-runtime 30mends the run after this long, whatever it is doing124
--stop-file PATHends the run once PATH exists: create it from anywhere143
--log-dir DIRdurable console.log, serial.log and events.jsonl

Either way the job running in the guest is killed first, and a stop event saying why goes to the log. Logs are appended as things happen, so they survive a crash of the run, and rotate at 8 MiB. A named native instance logs to snowglobe/logs/<name> in your cache directory unless --log-dir says otherwise.

  • console.log: the app's terminal, byte for byte.
  • serial.log: the guest's serial line, with the kernel's own messages (an OOM kill, a panic) and the job shell.
  • events.jsonl: one JSON object a line, each synced to disk: start, restored, job_start (the command, its environment variables' names but never their values, the timeout), job_end (status, timed_out, killed, milliseconds, the first 64 KiB of output) and stop (its reason: stop-file, max-runtime, guest-stopped or end, and the exit status).

Kills reach everything a job started. A job runs in a session of its own in the guest; when it runs out of time or the instance stops, it gets TERM, then KILL after 3 s, including whatever it put in the background. A process that makes a session of its own (a daemon that setsids) is out of reach, as it would be anywhere.

A job's result is its own word. A job's exit status comes back on a line marked with a one-time value the host types only to the job's launcher, so a job that merely prints a fake result isn't believed. A job runs as root in the guest, though: one determined to can still read that value from the launcher's memory and forge its own status, output and out/ files. That reaches only its own result. The host still enforces the time limit, and if a job leaves the guest's shell busy, later jobs fail with "the guest's shell is not answering" rather than run inside it.

Isolation

The first wall is the same everywhere: the guest is an emulated machine whose only ways out are its terminal, its files and, if the site has one, the network relay. Guest code never runs as host code.

BrowserNativeDocker
Around the emulatorthe browser's renderer sandboxno_new_privs, Landlock and seccomp on Linux; Seatbelt on macOSread-only root, no capabilities, memory and pid limits
Host filesnonereads the site; writes its cache, socket, logs and a job's workspacethe site, read-only; logs if given
New processesnone possiblerefusedinside the container only
Networkthe page's fetch() and WebSocketout the way your machine goes; private addresses offthe same, from the container
Needsa browsernothing (Linux or macOS)Docker

The native sandbox is strongest on Linux with Landlock (kernel 5.13 or later). A run says which parts it got; SNOWGLOBE_SANDBOX=strict refuses to run without all of them, and snowglobe-vm sandbox-check tries what a run must not be able to do and reports each.

Native runs of one site share its snapshot's memory: the first run writes it to the cache as a read-only image (in ram/, which only you can open), and every run maps it copy-on-write. A page a machine writes becomes its own, so one machine never sees another's memory, and each restore gets fresh randomness (the guest kernel reseeds on it). The image is named by a hash of the whole snapshot and checked before use (yours, read-only, one link); anything off and the run restores the usual way. SNOWGLOBE_RAM_IMAGE=off always does.

What none of them do: hide a site's contents from whoever runs it (the snapshot is theirs to read), or make network access safe beyond the rules above. Give a guest a network only when the work needs it.

Saving your work

A run in the browser saves the whole machine locally: memory, processes and the files it wrote, plus the terminal. Nothing is sent to a server.

  • Browser, automatic is the default: every minute while the page is visible (or every 5 or 15 minutes, or only on Save now).
  • Backup file downloads a .snowglobe file you can restore later. Backups can hold passwords, tokens and database contents and are not encrypted: keep them private, and restore only files you trust.
  • Folder, automatic saves into a folder you pick, on browsers with showDirectoryPicker() (desktop Chrome and Edge).

A refresh resumes the last completed save, not necessarily your latest changes, so use Save now before leaving important work. Saves are tied to the exact site build: after an incompatible rebuild, older ones are kept for export rather than restored.

How it works

  1. Boot layer. snowglobe adds a kernel, an initramfs that mounts the root filesystem over 9p, and BusyBox's init and getty with a terminal running your command, as files on top of your image's layers. Nothing runs in the image.
  2. Filesystem. The flattened layers become a 9p filesystem: a JSON tree and one compressed blob per unique file, fetched on demand. Files of 2 MiB and more are split into content-defined chunks, so a read fetches only what it covers.
  3. Snapshot. The guest boots headless until the app is ready, and the whole machine is saved. Visitors, snowglobe run, bundles and snowglobe mcp all restore it.
  4. Machines. Natively, the snapshot's memory becomes an image the host maps copy-on-write into every instance, and a template restores once and forks a machine per command, per snowglobe up or per service replica, each sharing what it doesn't write.
  5. Warm pack. The files read while booting and exercising are bundled into one download the page fetches in the background.

When a build goes wrong

  • "the boot stalled". Nothing was printed, fetched or computed for 5 minutes before the app was ready. The build prints every CPU's state and boots once more; twice usually means the app waits for something that never comes, such as --ready text it never prints. SNOWGLOBE_BOOT_STALL_SECS changes the limit, and 0 turns it off.
  • "not arm64". Build FROM alpine or arm64v8/alpine; on an x86 host, install QEMU's binfmt handlers first.
  • A request answers 502. In a page, the server doesn't allow CORS. Under snowglobe run the same request goes out from your machine instead.
  • A service doesn't start. There is no OpenRC: start it from the image's command.

CLI reference

snowglobe help prints all of this.

Commands

CommandWhat it does
build <dir | image>build a site from a Dockerfile directory, an image reference or an image archive
serve [dir]preview a built site (--port, default 8000)
run [dir | url]run a site in a terminal, in a container or natively
exec <name> <cmd>type a command into a running instance and print what it printed
attach <name>join a running instance (ctrl-] detaches)
ps · stop <name>list running instances · throw one away
sh <name> '<cmd>'run a command on a native instance's shell (--json, --timeout S)
cp <src> <dst>copy files in or out of a native instance (name:/path on one side)
mcp <dir | url>serve a site to an agent over MCP (--name, --keep, --log-dir)
bundle <dir | url>make a site one executable (-o, --target linux|darwin, --runtime)
pack <dir>pack a site into one tarball (-o site.tar.gz or .tar.zst)
pool <dir>let every site under a directory share one blob store
pwa <dir>make a site an installable app (--name, --short-name)
warm <dir> <reads.json…>add recorded reads to a site's warm pack
daemonthe orchestrator, on a Unix socket (--listen ADDR --token-file F for TCP; --memory 8G, --wait 30s)
template add <site> · ls · rmrestore a site once for machines to fork from (--name, --snapshot, --idle, --max-machines)
up <template>a machine forked from a template (--name, -p PORT, --ttl, --snapshot, --label K=V)
snapshot save <machine> · ls · rmsave a running machine whole, to start machines and templates from
service add <name> · ls · rma template's machines behind host ports, started on demand and scaled to zero (--template, -p, --min, --max, --idle, --concurrency, --state)
events · topthe daemon's events as they happen · the daemon live: memory, templates, services, machines
apply · diff · downget the daemon to what a fleet file says · show the plan · remove the fleet (-f snowglobe.toml)
tools · versionbuild or find the tools image · print the version

Build flags

FlagLabelWhat it sets
--out DIRoutput directory (default dist)
--cmd CMDthe program on the terminal (default: the image's entrypoint and command)
--ready TEXTsnowglobe.readyoutput that means the app is ready (default: 5 s of quiet)
--title TEXTsnowglobe.titlepage title
--memory MBsnowglobe.memoryguest RAM (default 512)
--cpus Nsnowglobe.cpusguest CPUs, 1 to 8 (default 1)
--build-cpus Nsnowglobe.build-cpusCPUs to boot on before the snapshot (default: the larger of --cpus and 4)
--exercise CMDsnowglobe.exercisea command whose reads are preloaded; repeatable (label: a JSON array)
--warm REGEXsnowglobe.warmextra guest paths to preload
--network fetchsnowglobe.networkgive the guest a network through the browser (default none)
--tunnel [HOST:]PORT=URLsnowglobe.tunnelscarry TCP to HOST:PORT over a WebSocket; repeatable
--tunnel-token URLsnowglobe.tunnel-tokenwhere a fresh token comes from, for {token} in tunnel URLs
--allow-privatelet the build reach loopback and private addresses
--builder Bdocker (default) or native
--sandboxsnapshot for running natively (run, bundle) rather than in a page
--no-chunksstore every file in one blob, however large

Run flags

FlagWhat it does
--name N · --detachname the instance · run it in the background
--runtime docker|nativein a container (default) or natively
--forkevery sh on a fresh copy of the machine (see --fork)
-p [IP:]HOST:GUESTforward a host port to a guest port (needs --network fetch)
--record-reads FILEwrite every file the guest reads, for warm
--tunnel · --allow-privatereplace the site's tunnels · let the guest reach this machine
--max-runtime · --stop-file · --log-dirsee limits, stopping and logs

Environment

VariableWhat it does
SNOWGLOBE_BUILDERdefault for --builder
SNOWGLOBE_RUNTIMEdefault for --runtime
SNOWGLOBE_VMthe snowglobe-vm to use instead of the release's
SNOWGLOBE_BOOTa kernel archive to use instead of the release's
SNOWGLOBE_TOOLS_BUILD=1always compile the tools image locally
SNOWGLOBE_SANDBOXstrict refuses to run without the whole native sandbox; off skips it
SNOWGLOBE_BOOT_STALL_SECShow long a boot may do nothing before it counts as stalled (default 300; 0: no limit)
SNOWGLOBE_CACHEwhere native runs keep what they learn and the snapshots' memory images (default ~/.cache/snowglobe)
SNOWGLOBE_RAM_IMAGE=offrestore into memory of the run's own instead of sharing the snapshot's image
ARMLESS_JIT=offkeep armless's interpreter instead of its native JIT
ARMLESS_JIT_PREWARM=offdon't compile what earlier runs recorded at start
HTTPS_PROXY · HTTP_PROXY · NO_PROXY · SSL_CERT_FILEpassed to snowglobe run's container