A standalone, stateful local emulator of the Fly.io Sprites API. Like LocalStack, but for Fly Sprites.
spritzer is the local target for chant's Fly lexicon — its Sprite activities (create, exec, checkpoint, restore) run against spritzer for offline, accountless checkpoint-as-compensation. See the chant docs and the Fly deploy tutorial it powers.
Testing a Sprites client, such as a workflow that checkpoints a sandbox before a risky step and restores it on failure, requires testing against state: a sprite whose filesystem a command mutates, a checkpoint that captures that filesystem, and a restore that rewinds to it. A schema mock holds no state, so it cannot model these behaviors. spritzer keeps sprites, their filesystems, and their checkpoints in memory and runs a small exec interpreter, so a client's checkpoint-as-compensation logic can be exercised end to end offline.
spritzer is wire-compatible with the in-process Sprites fake in the chant
lexicon (sprites-fake.ts): the endpoint shapes and the exec interpreter match
it, so the same integration suite passes against the spritzer container image.
- Stateful in-memory store of sprites keyed by name, each with a filesystem (path → contents) and an ordered list of checkpoints.
execis a control WebSocket atGET /v1/sprites/{id}/execspeaking the real Sprites SDK's framed protocol: each binary message is[streamID][payload](StreamStdin=0, StreamStdout=1, StreamStderr=2, StreamExit=3, StreamStdinEOF=4). Behind the frames a small scripted interpreter (echo > path,echo,cat,rm,true/false,./risky.sh, and an echo-back default) writes, overwrites, or fails a filesystem key so the result is observable.- Checkpoint / restore: create is
POST /v1/sprites/{id}/checkpoint(singular), streaming NDJSON progress and assigning a server version id (v1,v2, …) with an optional caller comment; the list is a bare array withcreate_timeandis_auto; restore takes a checkpoint id in the path, streams NDJSON, replaces the filesystem with that copy, and returns the sprite torunning. This is the checkpoint-as-compensation primitive. - A destroyed or missing sprite returns
404on any subsequent operation. - A
/_spritzer/healthendpoint reporting version and implemented paths. - Single static binary and distroless container image; no runtime dependencies.
Run the container:
docker run --rm -p 4290:4290 ghcr.io/intentius/spritzer:latestOr install with Go:
go install github.com/intentius/spritzer/cmd/spritzer@latest
spritzer # listens on :4290 by defaultPoint a Sprites client at it with the same environment variable the real client uses:
export SPRITES_BASE_URL=http://localhost:4290The listen address is configurable with -addr or SPRITZER_ADDR (default
:4290).
BASE=http://localhost:4290
# Create a sprite (its name is its id).
curl -s -X POST "$BASE/v1/sprites" -d '{"name":"demo"}'
# => {"id":"demo","url":"http://localhost:4290/s/demo"}
# Checkpoint the current state. The server assigns the version id and streams
# NDJSON progress; the id is on the terminal complete event.
curl -s -X POST "$BASE/v1/sprites/demo/checkpoint" -d '{"comment":"pre-run"}'
# => {"type":"info","data":"Creating checkpoint..."}
# {"type":"info","data":" ID: v1"}
# {"type":"complete","data":"Checkpoint v1 created successfully"}
# List the checkpoints (creation order) as a bare array.
curl -s "$BASE/v1/sprites/demo/checkpoints"
# => [{"id":"v1","comment":"pre-run","create_time":"2026-07-11T...Z","is_auto":false}]
# Restore rewinds the filesystem to the checkpoint, addressed by id in the path,
# streaming NDJSON progress.
curl -s -X POST "$BASE/v1/sprites/demo/checkpoints/v1/restore"
# => {"type":"info","data":"Restoring checkpoint v1..."}
# {"type":"complete","data":"Checkpoint v1 restored successfully"}exec is a control WebSocket at ws://<host>/v1/sprites/{id}/exec. Pass the
command as cmd query params (?cmd=echo&cmd=hi, or a single ?cmd=echo hi).
Every message is a binary frame [streamID][payload]: the server writes stdout
as [1]<bytes>, stderr as [2]<bytes>, then [3]<exitCodeByte>. So
echo hi yields [1]"hi\n" then [3]\x00 (exit 0), and ./risky.sh yields
[2]"risky.sh: failed\n" then [3]\x01 (exit 1).
| Capability | spritzer | Schema mock | Real Sprites |
|---|---|---|---|
| Sprite filesystem that exec mutates | Yes | No | Yes |
| Checkpoint / restore (rewind on failure) | Yes | No | Yes |
Destroyed-sprite 404 semantics |
Yes | No | Yes |
| Runs fully offline | Yes | Yes | No |
| Cost | Free | Free | Billed |
| Real sandboxes, images, code execution | No | No | Yes |
Implemented: create, the exec control WebSocket, checkpoint (NDJSON), list
checkpoints (bare array), get one checkpoint, restore-by-id (NDJSON), destroy,
and an inspection GET, plus a /_spritzer/health report. The full table is in
the API coverage docs.
The primary task runner is just; a Makefile
mirrors the same targets.
just build # compile
just test # go test ./...
just race # go test -race ./...
just lint # golangci-lint
just cover # coverage profile
just docs-serve # preview the doc siteContributions are welcome. See CONTRIBUTING.md and the Code of Conduct.
Licensed under the MIT License. Copyright (c) 2026 Intentius.