| 1 | --- |
| 2 | title: Phases |
| 3 | description: Alchemy programs run in two phases — Construction drives the deploy, Runtime handles requests. Knowing which is which is the key to writing Workers and Lambda Functions. |
| 4 | --- |
| 5 | |
| 6 | import DAG from "../../../components/DAG.astro"; |
| 7 | |
| 8 | Every alchemy program runs in two phases — plantime builds the plan, |
| 9 | runtime serves requests. [Runtime](/infrastructure-as-effects/runtime) |
| 10 | resources — Workers, Lambdas, Containers — express both in a single |
| 11 | program by **returning an Effect from inside an Effect**. Earlier |
| 12 | pages named the two halves the Construction phase and the Runtime |
| 13 | phase; this page pins down exactly when each one runs. |
| 14 | |
| 15 | ## Construction vs Runtime |
| 16 | |
| 17 | ```typescript |
| 18 | Cloudflare.Worker( |
| 19 | "Worker", |
| 20 | { main: import.meta.url }, |
| 21 | Effect.gen(function* () { |
| 22 | // ─── Construction phase ─── |
| 23 | const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket); |
| 24 | |
| 25 | return { |
| 26 | // ─── Runtime phase ─── |
| 27 | fetch: Effect.gen(function* () { |
| 28 | const obj = yield* bucket.get("key"); |
| 29 | return HttpServerResponse.text(yield* obj.text()); |
| 30 | }), |
| 31 | }; |
| 32 | }).pipe(Effect.provide(Cloudflare.R2.ReadWriteBucketBinding)), |
| 33 | ); |
| 34 | ``` |
| 35 | |
| 36 | | Phase | Code | When it runs | |
| 37 | | ------------ | ------ | ------------------------------------- | |
| 38 | | Construction | outer | At plantime **and** at cold start | |
| 39 | | Runtime | inner | Only inside a deployed handler | |
| 40 | |
| 41 | The `bucket` value is established once during Construction and captured |
| 42 | by the runtime closure. The constructor runs at most once per cold start; the |
| 43 | runtime body runs per request with everything already wired up. |
| 44 | Each phase has its own `Scope` with very different lifetimes — |
| 45 | [Instance scope vs request scope](/infrastructure-as-effects/runtime#instance-scope-vs-request-scope) |
| 46 | covers where cleanup can (and cannot) happen. |
| 47 | |
| 48 | The Runtime phase is the *only* place where `Alchemy.RuntimeContext` |
| 49 | is available. Any Effect whose requirements include `RuntimeContext` |
| 50 | can only execute inside the runtime closure — the type system |
| 51 | rejects it everywhere else. |
| 52 | [Layers](/infrastructure-as-effects/layers#the-types-hold-the-boundary) |
| 53 | builds on this split to keep whole services honest about where they |
| 54 | can run. |
| 55 | |
| 56 | ## What runs when |
| 57 | |
| 58 | <DAG |
| 59 | caption="Plantime (top) records bindings and builds the plan. Runtime (bottom) starts on cold start and runs the handler per request." |
| 60 | nodes={[ |
| 61 | { id: "Plan", label: "alchemy deploy", type: "plantime", tone: "accent" }, |
| 62 | { id: "ConstructP", label: "construct()", type: "records bindings" }, |
| 63 | { id: "Apply", label: "apply", type: "create / update" }, |
| 64 | { id: "Deploy", label: "deployed", type: "Worker / Lambda" }, |
| 65 | { id: "Cold", label: "cold start", type: "runtime", tone: "accent", layer: 0 }, |
| 66 | { id: "ConstructR", label: "construct()", type: "builds SDK clients", layer: 1 }, |
| 67 | { id: "Runtime", label: "fetch()", type: "per request", layer: 2 }, |
| 68 | ]} |
| 69 | edges={[ |
| 70 | { from: "Plan", to: "ConstructP" }, |
| 71 | { from: "ConstructP", to: "Apply" }, |
| 72 | { from: "Apply", to: "Deploy" }, |
| 73 | { from: "Cold", to: "ConstructR" }, |
| 74 | { from: "ConstructR", to: "Runtime" }, |
| 75 | ]} |
| 76 | /> |
| 77 | |
| 78 | - At **plantime**, the constructor runs to discover bindings — alchemy needs |
| 79 | to know which resources the handler will use so it can wire |
| 80 | permissions, env vars, and references. |
| 81 | - At **runtime cold start**, the constructor runs again — this time inside the |
| 82 | deployed Worker, where the same `bind()` calls return live SDK |
| 83 | clients backed by the deployed resource. |
| 84 | - The **runtime body** only runs in the deployed handler. It never |
| 85 | executes at plantime, so you can put real per-request work there |
| 86 | without affecting deploy speed. |
| 87 | |
| 88 | ## `ALCHEMY_PHASE` |
| 89 | |
| 90 | The current phase is exposed as the `ALCHEMY_PHASE` environment |
| 91 | variable / config key: |
| 92 | |
| 93 | | Value | Context | |
| 94 | | --------- | ------------------------------------------------------------------- | |
| 95 | | `plan` | Default. Running `alchemy deploy`, `alchemy plan`, or `alchemy dev`. | |
| 96 | | `runtime` | Running inside a deployed Worker or Lambda Function. | |
| 97 | |
| 98 | Most user code never reads this directly — but providers and |
| 99 | [Bindings](/infrastructure-as-effects/binding) use it internally to behave |
| 100 | differently across phases. |
| 101 | |
| 102 | ## `ALCHEMY_DEV` |
| 103 | |
| 104 | `alchemy dev` is a plantime phase, so `ALCHEMY_PHASE` is `plan` |
| 105 | during local development just like a regular deploy. To tell whether |
| 106 | you're running under `alchemy dev` specifically, read the |
| 107 | `ALCHEMY_DEV` config key. The CLI sets `ALCHEMY_DEV=true` on the dev |
| 108 | process; every other entrypoint leaves it unset, so it defaults to |
| 109 | `false`. |
| 110 | |
| 111 | ```typescript |
| 112 | import { ALCHEMY_DEV } from "alchemy"; |
| 113 | |
| 114 | Effect.gen(function* () { |
| 115 | if (yield* ALCHEMY_DEV) { |
| 116 | // local-dev-only behavior |
| 117 | } |
| 118 | }); |
| 119 | ``` |
| 120 | |
| 121 | ## The `__ALCHEMY_RUNTIME__` guard |
| 122 | |
| 123 | A binding's contract is a `Binding.Service`; the guarded setup |
| 124 | Effect lives in the implementation Layer you provide, not the |
| 125 | contract. That setup Effect does two things — and the phase decides |
| 126 | how much of it runs: |
| 127 | |
| 128 | | Work | Phase | Job | |
| 129 | | ----------------------- | ----- | -------------------------------------------------------- | |
| 130 | | typed client | both | The lightweight typed SDK that ships with the bundle. | |
| 131 | | `host.bind(...)` wiring | plan | Deploy-time registration of IAM / env / native bindings. | |
| 132 | |
| 133 | The deploy-time wiring is fenced behind `if (!globalThis.__ALCHEMY_RUNTIME__)`. |
| 134 | At plantime the guard is open, so calling `bind()` records what the |
| 135 | function will need. At runtime the global flag is set, so the guard is |
| 136 | skipped and `bind()` returns just the typed client. The runtime bundle |
| 137 | stays small because the planning branch never executes. |
| 138 | |
| 139 | Which wiring the guard protects is decided by the implementation |
| 140 | Layer you provided |
| 141 | ([Bindings](/infrastructure-as-effects/binding#a-contract-and-a-layer)). |
| 142 | |
| 143 | This is also how alchemy can let you write `bucket.get(...)` inside a |
| 144 | Worker without bundling AWS / Cloudflare provisioning code: the |
| 145 | provisioning lives behind the guard, never on the runtime path. |
| 146 | |
| 147 | ## Why this matters |
| 148 | |
| 149 | The Construction/Runtime split lets you write code that: |
| 150 | |
| 151 | 1. **Resolves infrastructure references at deploy time** — bindings |
| 152 | know which bucket ARN, queue URL, etc. to inject. |
| 153 | 2. **Initializes SDK clients once at cold start** — not on every |
| 154 | request. |
| 155 | 3. **Handles requests with a pre-configured context** — the `bucket` |
| 156 | variable in the runtime body already knows which resource to |
| 157 | talk to. |
| 158 | |
| 159 | ## Where next |
| 160 | |
| 161 | - [Circular Bindings](/infrastructure-as-effects/circular-bindings) — the two-pass plan this phase split makes possible. Next page. |
| 162 | - [Layers](/infrastructure-as-effects/layers) — `RuntimeContext` as a type-level boundary; infrastructure behind service interfaces. |
| 163 | - [State Store](/state-store) — where deploy results persist between runs. |