| 1 | --- |
| 2 | title: "Part 4: Stages" |
| 3 | description: Deploy isolated dev, staging, and prod instances of your stack with --stage, and tune resources per stage. |
| 4 | sidebar: |
| 5 | order: 4 |
| 6 | --- |
| 7 | |
| 8 | import Terminal from "../../../../components/Terminal.astro"; |
| 9 | |
| 10 | In [Part 3](/aws/tutorial/part-3) you wrote integration tests against |
| 11 | a deployed stack. But so far everything has landed in a single |
| 12 | environment. In this part you'll use **stages** — isolated instances |
| 13 | of the same Stack — to give every developer, environment, and (in |
| 14 | [Part 5](/aws/tutorial/part-5)) every pull request its own copy of |
| 15 | the infrastructure. |
| 16 | |
| 17 | ## The default stage |
| 18 | |
| 19 | You've been using stages all along. If you don't pass `--stage`, |
| 20 | Alchemy deploys to **`live_$USER`** (e.g. `live_sam`): |
| 21 | |
| 22 | ```sh |
| 23 | $ whoami |
| 24 | sam |
| 25 | |
| 26 | $ bun alchemy deploy |
| 27 | # deploys to stage `live_sam` |
| 28 | ``` |
| 29 | |
| 30 | Each developer on your team automatically gets a personal sandbox |
| 31 | without any config. The resolution order is: |
| 32 | |
| 33 | 1. `--stage <name>` flag |
| 34 | 2. `$ALCHEMY_STAGE` environment variable |
| 35 | 3. `live_${USER}` (or `live_${USERNAME}` on Windows) |
| 36 | 4. `live_unknown` if no user is set |
| 37 | |
| 38 | ## Deploy a second stage |
| 39 | |
| 40 | Pass `--stage` to deploy a completely separate instance of the same |
| 41 | program: |
| 42 | |
| 43 | ```sh |
| 44 | bun 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 | |
| 51 | Proceed? |
| 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 | |
| 59 | Everything is created fresh — a new bucket, a new function, a new |
| 60 | Function URL — because `staging` has never been deployed before. |
| 61 | Your `dev_sam` deployment is untouched. |
| 62 | |
| 63 | ## How stages isolate |
| 64 | |
| 65 | Each 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 | |
| 74 | Because of this, deploying or destroying one stage **never touches** |
| 75 | another: |
| 76 | |
| 77 | ```sh |
| 78 | alchemy deploy --stage prod |
| 79 | alchemy deploy --stage pr-42 |
| 80 | alchemy destroy --stage pr-42 # only removes pr-42 resources |
| 81 | ``` |
| 82 | |
| 83 | Stage names must match `[a-z0-9][-_a-z0-9]*`. |
| 84 | |
| 85 | ## Configure per stage |
| 86 | |
| 87 | The same program deploys to every stage, but you'll often want |
| 88 | different knobs per environment — more Lambda memory in prod, say. |
| 89 | `Stack.useSync` gives module-scope resources a synchronous accessor |
| 90 | to the surrounding Stack context, including the current `stage`: |
| 91 | |
| 92 | ```diff lang="typescript" |
| 93 | // src/api.ts |
| 94 | import * as AWS from "alchemy/AWS"; |
| 95 | import * as S3 from "alchemy/AWS/S3"; |
| 96 | +import { Stack } from "alchemy/Stack"; |
| 97 | import * as Effect from "effect/Effect"; |
| 98 | // ... |
| 99 | |
| 100 | export 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 | |
| 114 | Now `alchemy deploy --stage prod` provisions a 1024 MB function while |
| 115 | every other stage stays at 512 MB — one program, per-stage |
| 116 | configuration, no separate config files. |
| 117 | |
| 118 | ## Destroy a stage |
| 119 | |
| 120 | When you're done with an environment, tear down just that stage: |
| 121 | |
| 122 | ```sh |
| 123 | bun alchemy destroy --stage staging |
| 124 | ``` |
| 125 | |
| 126 | The plan lists everything in `staging` for deletion and leaves every |
| 127 | other stage alone. |
| 128 | |
| 129 | ## Stages in tests |
| 130 | |
| 131 | The test harness from Part 3 defaults to `test_$USER` (e.g. |
| 132 | `test_sam`). Override it per file — or per call — which is how |
| 133 | multiple CI runs can test in parallel against the same AWS account |
| 134 | without colliding: |
| 135 | |
| 136 | ```diff lang="typescript" |
| 137 | // test/integ.test.ts |
| 138 | const { 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 | |
| 145 | See [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 | |
| 150 | Stages isolate **what** is deployed (state, physical names). |
| 151 | [Profiles](/environments/profiles) isolate **how** Alchemy authenticates |
| 152 | to 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 |
| 154 | credentials across many stages: |
| 155 | |
| 156 | ```sh |
| 157 | alchemy deploy --stage prod --profile prod |
| 158 | alchemy deploy --stage pr-42 --profile default |
| 159 | ``` |
| 160 | |
| 161 | ## Recap |
| 162 | |
| 163 | You 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 | |
| 171 | In [Part 5](/aws/tutorial/part-5), you'll put stages to work in CI: |
| 172 | GitHub Actions deploys `prod` on pushes to `main` and an isolated |
| 173 | `pr-<n>` preview stage for every pull request. |