1---
2title: "Part 4: Stages"
3description: Deploy isolated dev, staging, and prod instances of your stack with --stage, and tune resources per stage.
4sidebar:
5 order: 4
6---
7
8import Terminal from "../../../../components/Terminal.astro";
9
10In [Part 3](/aws/tutorial/part-3) you wrote integration tests against
11a deployed stack. But so far everything has landed in a single
12environment. In this part you'll use **stages** — isolated instances
13of the same Stack — to give every developer, environment, and (in
14[Part 5](/aws/tutorial/part-5)) every pull request its own copy of
15the infrastructure.
16
17## The default stage
18
19You've been using stages all along. If you don't pass `--stage`,
20Alchemy deploys to **`live_$USER`** (e.g. `live_sam`):
21
22```sh
23$ whoami
24sam
25
26$ bun alchemy deploy
27# deploys to stage `live_sam`
28```
29
30Each developer on your team automatically gets a personal sandbox
31without any config. The resolution order is:
32
331. `--stage <name>` flag
342. `$ALCHEMY_STAGE` environment variable
353. `live_${USER}` (or `live_${USERNAME}` on Windows)
364. `live_unknown` if no user is set
37
38## Deploy a second stage
39
40Pass `--stage` to deploy a completely separate instance of the same
41program:
42
43```sh
44bun alchemy deploy --stage staging
45```
46
47<Terminal content={`[u]Plan[/u]: [g]2 to create[/g]
48[g]+[/g] [b]Api[/b] [d](AWS.Lambda.Function)[/d]
49[g]+[/g] [b]Bucket[/b] [d](AWS.S3.Bucket)[/d]
50
51Proceed?
52◉ Yes ○ No
53[g]✓[/g] [b]Bucket[/b] [d](AWS.S3.Bucket)[/d] [g]created[/g]
54[g]✓[/g] [b]Api[/b] [d](AWS.Lambda.Function)[/d] [g]created[/g]
55{
56[s2]url: "https://def456uvw.lambda-url.us-east-1.on.aws",
57}`} />
58
59Everything is created fresh — a new bucket, a new function, a new
60Function URL — because `staging` has never been deployed before.
61Your `dev_sam` deployment is untouched.
62
63## How stages isolate
64
65Each stage gets its own:
66
67- **State** — the persisted record of what's deployed, kept
68 separately per stage in the `AWS.state()` S3 bucket
69- **Physical names** — `myapp-prod-bucket-7d4e` vs
70 `myapp-dev_sam-bucket-9b2c`; the stage is baked into every
71 generated resource name
72- **Logs and metrics** — scoped per deployed function
73
74Because of this, deploying or destroying one stage **never touches**
75another:
76
77```sh
78alchemy deploy --stage prod
79alchemy deploy --stage pr-42
80alchemy destroy --stage pr-42 # only removes pr-42 resources
81```
82
83Stage names must match `[a-z0-9][-_a-z0-9]*`.
84
85## Configure per stage
86
87The same program deploys to every stage, but you'll often want
88different knobs per environment — more Lambda memory in prod, say.
89`Stack.useSync` gives module-scope resources a synchronous accessor
90to the surrounding Stack context, including the current `stage`:
91
92```diff lang="typescript"
93// src/api.ts
94import * as AWS from "alchemy/AWS";
95import * as S3 from "alchemy/AWS/S3";
96+import { Stack } from "alchemy/Stack";
97import * as Effect from "effect/Effect";
98// ...
99
100export default class Api extends AWS.Lambda.Function<Api>()(
101 "Api",
102- { main: import.meta.url, functionUrl: true },
103+ Stack.useSync((stack) => ({
104+ main: import.meta.url,
105+ functionUrl: true,
106+ memory: stack.stage === "prod" ? 1024 : 512,
107+ })),
108 Effect.gen(function* () {
109 /* ... */
110 }),
111) {}
112```
113
114Now `alchemy deploy --stage prod` provisions a 1024 MB function while
115every other stage stays at 512 MB — one program, per-stage
116configuration, no separate config files.
117
118## Destroy a stage
119
120When you're done with an environment, tear down just that stage:
121
122```sh
123bun alchemy destroy --stage staging
124```
125
126The plan lists everything in `staging` for deletion and leaves every
127other stage alone.
128
129## Stages in tests
130
131The test harness from Part 3 defaults to `test_$USER` (e.g.
132`test_sam`). Override it per file — or per call — which is how
133multiple CI runs can test in parallel against the same AWS account
134without colliding:
135
136```diff lang="typescript"
137// test/integ.test.ts
138const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
139 providers: AWS.providers(),
140 state: AWS.state(),
141+ stage: `pr-${process.env.PR_NUMBER}`,
142});
143```
144
145See [Test harness → stage](/testing/test-harness#stage) for the per-call form
146(`deploy(Stack, { stage: "ci-pr-42" })`).
147
148## Stage vs profile
149
150Stages isolate **what** is deployed (state, physical names).
151[Profiles](/environments/profiles) isolate **how** Alchemy authenticates
152to AWS. They're orthogonal — you can pair a `prod` stage with a
153`prod` profile that points at a separate AWS account, or use the same
154credentials across many stages:
155
156```sh
157alchemy deploy --stage prod --profile prod
158alchemy deploy --stage pr-42 --profile default
159```
160
161## Recap
162
163You now have:
164
165- A personal `dev_<user>` stage that every plain `alchemy deploy`
166 targets
167- A `staging` stage deployed (and destroyed) with `--stage`
168- Per-stage Lambda memory via `Stack.useSync`
169- Tests pinned to their own stage
170
171In [Part 5](/aws/tutorial/part-5), you'll put stages to work in CI:
172GitHub Actions deploys `prod` on pushes to `main` and an isolated
173`pr-<n>` preview stage for every pull request.