Skip to content

Part 6: Stages and CI

Terminal window
pnpm alchemy deploy --stage staging

Each stage builds its own Golden snapshot and its own Worker, with its own Boat key, so changing the snapshot in one stage never reaches another. Without --stage, you deploy to live_$USER.

Stages share your Boat account. Sandboxes a stage’s Worker creates carry the stage as a label (alchemy.stage), so you can list one stage’s sandboxes without touching another’s:

const staging = yield* sandboxes.list({ labels: { "alchemy.stage": "staging" } });

Destroying a stage deletes its snapshot and its Worker’s key, and leaves its sandboxes to stop on their timers.

CI runs start from an empty disk, so store state where every run can read it:

alchemy.run.ts
{
providers: Layer.mergeAll(Cloudflare.providers(), Boat.providers()),
state: Alchemy.localState(),
state: Cloudflare.state(),
},

Create a key in Boat’s dashboard (API Keys) with the admin preset: deploying creates the Worker’s own key. Add it to the repository’s secrets as BOAT_API_KEY. Alchemy reads it instead of a profile.

.github/workflows/deploy.yml
name: Deploy
on:
pull_request:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
env:
BOAT_API_KEY: ${{ secrets.BOAT_API_KEY }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
STAGE: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || 'prod' }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun alchemy deploy --stage "$STAGE" --yes

Each pull request gets its own stage, and so its own base: a pull request that changes Golden rebuilds it for that stage only. Boat has no keyless CI login, so the workflow uses the API key.

.github/workflows/cleanup.yml
name: Cleanup
on:
pull_request:
types: [closed]
jobs:
destroy:
runs-on: ubuntu-latest
env:
BOAT_API_KEY: ${{ secrets.BOAT_API_KEY }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun alchemy destroy --stage "pr-${{ github.event.number }}" --yes

Destroying the stage deletes its snapshot and the Worker’s key.