Stages
A stage is an isolated instance of a Stack. Every deploy targets exactly one stage, and resources from different stages never overlap. This is how Alchemy gives every developer their own sandbox, every PR its own preview, and production its own dedicated environment — all from one program.
The default stage
Section titled “The default stage”If you don’t pass --stage, alchemy deploy uses live_$USER
(e.g. live_sam) and alchemy dev uses dev_$USER (e.g.
dev_sam). Each developer gets a personal cloud sandbox and a
separate local-dev sandbox without any config — so a bare alchemy dev cannot replace resources you deployed. Existing default alchemy dev stacks keep the dev_$USER name. Test.make uses
test_$USER the same way, so two people running the suite
against one account don’t collide.
$ whoamisam
$ alchemy deploy# deploys to stage `live_sam`
$ alchemy dev# emulates locally as stage `dev_sam`
$ alchemy destroy --stage dev_sam# tears down the local-dev stage--stage and $ALCHEMY_STAGE still work on both commands. Pointing
alchemy dev at the same stage as a deploy (--stage prod, or a
shared $ALCHEMY_STAGE) is a live ⇄ local replacement of that stage.
The resolution order is:
--stage <name>flag$ALCHEMY_STAGEenvironment variable (process env,--env-file, or.env)live_${USER}for deploy / destroy / plan / logs / drift (dev_${USER}foralchemy dev)live_unknown/dev_unknownif no user is set
Common stage patterns
Section titled “Common stage patterns”| Stage | Purpose |
|---|---|
live_<user> |
Per-developer cloud sandbox (alchemy deploy default) |
dev_<user> |
Per-developer local-dev sandbox (alchemy dev default) |
test_<user> |
Per-developer test sandbox (Test.make default) |
pr-<n> |
Per-pull-request preview environment |
staging |
Shared pre-production |
prod |
Production |
Stage names must match [a-z0-9][-_a-z0-9]*.
alchemy deploy --stage prodalchemy deploy --stage pr-42alchemy destroy --stage pr-42Isolation
Section titled “Isolation”Each stage gets its own:
- State file — the persisted record of what’s deployed
- Physical names —
myapp-prod-bucket-abc123vsmyapp-live_sam-bucket-9b2c - Logs and metrics — scoped per deployed function/worker
Because of this, deploying or destroying one stage never touches another:
$ alchemy deploy --stage live_sam # -> myapp-live_sam-photos-a3f1$ alchemy deploy --stage pr-147 # -> myapp-pr_147-photos-9b2c$ alchemy deploy --stage prod # -> myapp-prod-photos-7d4e
$ alchemy destroy --stage pr-147 # only removes pr-147 resourcesPer-stage configuration
Section titled “Per-stage configuration”The current stage is exposed through the Stack service, so the
same program can branch on it:
import { Stack } from "alchemy/Stack";
Effect.gen(function* () { const stack = yield* Stack;
const queue = yield* SQS.Queue("Jobs").pipe( RemovalPolicy.retain(stack.stage === "prod"), );});For resources declared at module scope, use
Stack.useSync:
export class JobFunction extends AWS.Lambda.Function<JobFunction>()( "JobFunction", Stack.useSync((stack) => ({ main: import.meta.url, memory: stack.stage === "prod" ? 1024 : 512, })),) {}Pull request previews
Section titled “Pull request previews”A common pattern in CI: spin up a fresh stage for every PR, comment the URL on the PR, and tear it down on merge.
- run: alchemy deploy --stage pr-${{ github.event.number }} --yes
# on PR close:- run: alchemy destroy --stage pr-${{ github.event.number }} --yesSee the CI guide for a complete example.
Stage vs profile
Section titled “Stage vs profile”Stages isolate what is deployed (state, physical names).
Profiles isolate how alchemy authenticates
to your cloud providers. They’re orthogonal — you can pair a prod
stage with a prod profile, or use the same credentials across many
stages:
alchemy deploy --stage prod --profile prodalchemy deploy --stage pr-42 --profile defaultFor the full set of CLI flags, see the CLI reference.