snowglobe

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.