Fly Deploy with Checkpoint Rollback
Show the whole prompt
Read https://intentius.io/chant/tutorials/fly-deploy-rollback/.
In `examples/fly-deploy-rollback`, run `npm install`. Export `FLY_FLAPS_BASE_URL=http://localhost:4280` and `SPRITES_BASE_URL=http://localhost:4290`, and run `npx chant run fly-deploy --no-record` against the emulators.
Then run `npx chant run fly-deploy-guarded --no-record`, and show me the output of both runs, with the rollback to the checkpoint in the second.
Keep both emulator overrides set and set no Fly or Sprites token.
Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.Optional: hand this page to your coding agent
Read https://intentius.io/chant/tutorials/fly-deploy-rollback/.In `examples/fly-deploy-rollback`, run `npm install`. Export `FLY_FLAPS_BASE_URL=http://localhost:4280` and `SPRITES_BASE_URL=http://localhost:4290`, and run `npx chant run fly-deploy --no-record` against the emulators.Then run `npx chant run fly-deploy-guarded --no-record`, and show me the output of both runs, with the rollback to the checkpoint in the second.Keep both emulator overrides set and set no Fly or Sprites token.Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.The fly-deploy-rollback
example is for teams already on Fly Machines. It wraps a transactional rollback
boundary around a Fly deploy, using a Sprite checkpoint as the commit point:
- declarative infra-as-code for your Machines: a Fly
Appand aMachineyou author in TypeScript, applied straight to the Machines API; - a Sprite sandbox (Fly’s stateful, checkpointable VM) whose checkpoint is a clean rewind point;
- checkpoint-as-compensation: when a risky post-deploy step fails, restoring the checkpoint rewinds the whole sandbox to its known-good state in roughly the time a VM restore takes, instead of a hand-written undo.
The whole flow runs offline against the emulators (no Fly account, no cost), and points at real Fly + real Sprites by changing two environment variables.
| Op | Point |
|---|---|
fly-deploy | the happy path — checkpoint, build, deploy, verify, tear down |
fly-deploy-guarded | the rollback climax — a risky follow-up step fails and the Sprite rewinds |
It composes two smaller examples:
local-fly
(App + Machine -> flaps via flyApply) and
sprites-agent-task
(checkpoint-as-compensation on a Sprite).
The infra to deploy
Section titled “The infra to deploy”src/infra.ts is one Fly App and one Machine, the smallest complete
deploy:
import { App, Machine, MachineConfig, MachineGuest, Fly } from "@intentius/chant-lexicon-fly";
// org_slug is required by the Machines API (real Fly rejects app creation// without it). Fly.OrgSlug resolves from FLY_ORG at build time, default// "personal".const app = new App({ name: "fly-deploy-demo", org_slug: Fly.OrgSlug });
const web = new Machine({ name: "web", region: "iad", config: new MachineConfig({ image: "flyio/hellofly:latest", guest: new MachineGuest({ cpu_kind: "shared", cpus: 1, memory_mb: 256 }), }),});build:fly serializes these into the flaps create bodies flyApply POSTs: the
App into POST /v1/apps { app_name, org_slug }, the Machine into
POST /v1/apps/fly-deploy-demo/machines { name, region, config }. The serializer
stamps managed-by: chant into config.metadata, the ownership marker the
owned-only prune reads back.
Prerequisites
Section titled “Prerequisites”- Node.js and
npm. - Docker for the offline run, which boots the
spritzerandmudflapsemulators in containers. Real mode needs no Docker.
Run it offline
Section titled “Run it offline”Point the two activity endpoints at the local emulators and run the Op:
cd examples/fly-deploy-rollbacknpm install
export FLY_FLAPS_BASE_URL=http://localhost:4280export SPRITES_BASE_URL=http://localhost:4290
chant run fly-deploychant run fly-deploy-guardedfly-deploy exits ok. The phases run with no raw shell: boot the emulators, create
the Sprite sandbox and seed its known-good state, checkpoint it, build the flaps
plan, flyApply the App + Machine (waiting each machine to started), verify,
then tear down.
Watch the bad deploy roll back
Section titled “Watch the bad deploy roll back”fly-deploy-guarded is the same setup, then a risky follow-up step (./risky.sh, run
in the Sprite) corrupts the sandbox’s state and exits non-zero. The failing
RiskyChange phase triggers the Op-level onFailure Rollback. It restores
the known-good checkpoint to rewind the whole sandbox, then proves the rewind
and cleans up. It exits non-zero on purpose:
[phase] Deploy ✓ flyApply(planPath=dist/fly.json) 165ms[phase] RiskyChange ✗ spriteExec(id=deploy-sandbox, cmd=./risky.sh) 5ms sprite deploy-sandbox exec "./risky.sh" exited 1: risky.sh: failed[phase] Rollback ✓ spriteRestore(id=deploy-sandbox, comment=known-good) 3ms ✓ spriteExec(id=deploy-sandbox, cmd=cat /work/state) 3ms [outcome] state=known-good ✓ spriteDestroy(id=deploy-sandbox) 1ms ✓ flapsDown() 149ms ✓ spritesDown() 132msOp "fly-deploy-guarded" failed after 1.4sA Sprite checkpoint is the transactional boundary, so recovery is a restore, not
a hand-written inverse action. The checkpoint comment and Sprite id are
static strings, so nothing has to be threaded out of a prior phase.
Run it against real Fly + real Sprites
Section titled “Run it against real Fly + real Sprites”Drop the two emulator overrides and provide tokens. No code change:
unset FLY_FLAPS_BASE_URL # flyApply falls through to real Fly (api.machines.dev)unset SPRITES_BASE_URL # the sprite steps fall through to real Sprites
export FLY_API_TOKEN=... # a Fly deploy tokenexport SPRITES_API_TOKEN=... # a Sprites token
chant run fly-deployEach env var also gates its emulator, so real mode boots no container (no Docker
required) and the phase list is just Sandbox -> Checkpoint -> Build -> Deploy -> Teardown. The deploy is orchestrated by the Op in both modes; the Sprite is the
rollback boundary, not the deploy host. See the
example README
for the full real-vs-modeled breakdown.