Part 4: Stages
In Part 3 you tested a deployed Stack. So far everything lands in one environment. Stages are separate copies of the same Stack: one per developer, per environment, and (in Part 5) per pull request.
The default stage
Section titled “The default stage”Without --stage, Alchemy deploys to live_$USER:
$ whoamisam
$ bun alchemy deploy# deploys to stage `live_sam`Every developer gets a personal copy with no setup. The stage comes from, in order:
- the
--stage <name>flag - the
ALCHEMY_STAGEenvironment variable live_${USER}(orlive_${USERNAME}on Windows)
Deploy a second stage
Section titled “Deploy a second stage”bun alchemy deploy --stage stagingPlan: 2 to create + Api (GCP.Run.Service) + Bucket (GCP.Storage.Bucket) Proceed? ◉ Yes ○ No ✓ Bucket (GCP.Storage.Bucket) created ✓ Api (GCP.Run.Service) created { url: "https://myapp-staging-api-d4e5f6-uc.a.run.app", }
staging gets its own Bucket, its own service, its own service
account, and its own URL. live_sam is untouched.
How stages stay apart
Section titled “How stages stay apart”Each stage has its own state and its own resource names. The stage is
part of every generated name, so prod and live_sam never collide in
the same project.
Deploying or destroying one stage never touches another:
bun alchemy deploy --stage prodbun alchemy deploy --stage pr-42bun alchemy destroy --stage pr-42 # only removes pr-42Stage names must match [a-z0-9][-_a-z0-9]*.
Configure per stage
Section titled “Configure per stage”Keep one warm instance in prod so the first request after a quiet
period doesn’t wait for a cold start. Stack.useSync gives the props
the current stage:
import * as GCP from "@alchemy.run/gcp";import { Stack } from "alchemy/Stack";import * as Effect from "effect/Effect";
export default class Api extends GCP.Function<Api>()( "Api", { main: import.meta.url, invokerIamDisabled: true }, Stack.useSync((stack) => ({ main: import.meta.url, invokerIamDisabled: true, scaling: { minInstanceCount: stack.stage === "prod" ? 1 : 0 }, })), Effect.gen(function* () { // ... }),) {}alchemy deploy --stage prod keeps one instance running. Every other
stage scales to zero.
Destroy a stage
Section titled “Destroy a stage”bun alchemy destroy --stage stagingThe plan lists everything in staging for deletion, including the
service account Alchemy created for the service.
Stages in tests
Section titled “Stages in tests”Tests default to test_$USER. Set the stage per file so parallel CI
runs don’t collide:
const { test, beforeAll, afterAll, deploy, destroy } = Test.make({ providers: GCP.providers(), state: GCP.state(), stage: `pr-${process.env.PR_NUMBER}`,});See Test harness → stage for the per-call form.
Stages and projects
Section titled “Stages and projects”Stages separate what is deployed. Profiles
choose where: which GCP project and credentials. Pair a prod
stage with a prod profile pointing at a separate project:
bun alchemy deploy --stage prod --profile prodbun alchemy deploy --stage pr-42 --profile defaultYou now have:
- A personal
live_<user>stage for every plainalchemy deploy - A
stagingstage deployed and destroyed with--stage - A warm instance in prod only, via
Stack.useSync - Tests pinned to their own stage
In Part 5, GitHub Actions deploys prod on
every push to main and a pr-<n> preview for every pull request.