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
--exercisecommand 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.