Skip to content

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.

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.

Terminal window
$ whoami
sam
$ 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:

  1. --stage <name> flag
  2. $ALCHEMY_STAGE environment variable (process env, --env-file, or .env)
  3. live_${USER} for deploy / destroy / plan / logs / drift (dev_${USER} for alchemy dev)
  4. live_unknown / dev_unknown if no user is set
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]*.

Terminal window
alchemy deploy --stage prod
alchemy deploy --stage pr-42
alchemy destroy --stage pr-42

Each stage gets its own:

  • State file — the persisted record of what’s deployed
  • Physical namesmyapp-prod-bucket-abc123 vs myapp-live_sam-bucket-9b2c
  • Logs and metrics — scoped per deployed function/worker

Because of this, deploying or destroying one stage never touches another:

Terminal window
$ 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 resources

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,
})),
) {}

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.

.github/workflows/preview.yml
- run: alchemy deploy --stage pr-${{ github.event.number }} --yes
# on PR close:
- run: alchemy destroy --stage pr-${{ github.event.number }} --yes

See the CI guide for a complete example.

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:

Terminal window
alchemy deploy --stage prod --profile prod
alchemy deploy --stage pr-42 --profile default

For the full set of CLI flags, see the CLI reference.