1---
2title: Resource Lifecycle
3description: How alchemy plans, applies, replaces, and destroys resources — and how to think about idempotency and recovery.
4---
5
6import Terminal from "../../../components/Terminal.astro";
7import DAG from "../../../components/DAG.astro";
8
9Every [Resource](/infrastructure-as-code/resource) goes through the same lifecycle:
10**plan → reconcile → (replace) → delete**. The plan classifies each
11resource as create, update, replace, delete, or no-op, but the
12provider implements a single `reconcile` function that converges the
13cloud's actual state to what's declared — whether that's the first
14provisioning, a routine update, or an adoption takeover. For the CLI
15flags that drive these operations, see the
16[CLI reference](/cli).
17
18## The lifecycle, end-to-end
19
20<DAG
21 nodes={[
22 { id: "Plan", tone: "accent" },
23 { id: "Reconcile (create)" },
24 { id: "Reconcile (update)" },
25 { id: "Replace", tone: "warn" },
26 { id: "Delete", tone: "danger" },
27 { id: "Noop" },
28 ]}
29 edges={[
30 { from: "Plan", to: "Reconcile (create)", label: "new" },
31 { from: "Plan", to: "Reconcile (update)", label: "changed" },
32 { from: "Plan", to: "Replace", label: "breaking" },
33 { from: "Plan", to: "Delete", label: "removed" },
34 { from: "Plan", to: "Noop", label: "unchanged" },
35 { from: "Replace", to: "Delete", dashed: true },
36 ]}
37/>
38
39Both create and update intents resolve to the provider's single
40`reconcile` function. Replace runs `reconcile` against a fresh
41instance id and then deletes the old generation. The same engine
42drives `alchemy deploy`, `alchemy destroy`, and `alchemy dev`.
43
44## Plan
45
46When you run `alchemy deploy`, alchemy first **plans** the change.
47It compares the desired state (what your code declares) against the
48last persisted state and classifies each resource:
49
50- **Create** (`+`) — declared in code, not in state
51- **Update** (`~`) — declared and persisted, but properties differ
52- **Replace** (`±`) — change requires destroy-and-recreate
53- **Delete** (`-`) — persisted but no longer declared
54- **No-op** (`•`) — unchanged
55
56<Terminal content={`[u]Plan[/u]: [g]1 to create[/g], [y]1 to update[/y]
57
58[g]+[/g] [b]Queue[/b] [d](AWS.SQS.Queue)[/d]
59[y]~[/y] [b]Worker[/b] [d](Cloudflare.Worker)[/d]
60[d]•[/d] [b]Bucket[/b] [d](Cloudflare.R2.Bucket)[/d]`} />
61
62The classification comes from each provider's `diff` function. See
63[Provider › diff](/infrastructure-as-code/provider#diff) for how providers decide
64between in-place updates and replacements.
65
66Use `alchemy plan` (or `alchemy deploy --dry-run`) to see the plan
67without applying it.
68
69## Reconcile
70
71Whether a resource is being created for the first time, updated in
72place, or adopted from existing infrastructure, alchemy calls a
73single function: `provider.reconcile`.
74
75Reconcile must be **convergent**: given the desired props, it
76observes live cloud state and brings it to that state regardless of
77starting point. It must also be safe to retry — a partial reconcile
78that crashed midway resumes correctly on the next run, because
79physical names are deterministic from `stack/stage/logical-id` and
80the reconciler finds the existing resource instead of duplicating
81it. The parameter contract (`news`/`output`/`olds`/`bindings`) and
82the observe → ensure → sync shape are the provider's concern — see
83[Provider › reconcile](/infrastructure-as-code/provider#reconcile).
84
85A second pass — **convergence** — re-runs `reconcile` for any
86resource whose inputs changed because an upstream output changed
87mid-deploy.
88
89## Replace
90
91Some property changes can't be applied in place — for example,
92changing a DynamoDB table's partition key. The provider's `diff`
93returns `{ action: "replace" }`, and alchemy:
94
951. Creates a new resource with a new instance ID
962. Updates downstream resources to reference the new resource
973. Deletes the old resource
98
99Because new and old coexist briefly, dependents get a clean cutover
100without downtime.
101
102Changing a **logical ID** also plans a replacement — if you merely
103renamed the resource, declare it with `renamedFrom` instead; see
104[Renaming Resources](/infrastructure-as-code/renaming).
105
106## Delete
107
108`provider.delete` is called when a resource disappears from your
109code, when a replacement supersedes it, or when you run
110`alchemy destroy`. Like create, delete must be **idempotent**:
111deleting an already-gone resource is a success, not an error.
112
113`alchemy destroy` is just a plan where every persisted resource is
114marked for deletion. Resources are removed in **reverse dependency
115order** — dependents go first.
116
117<Terminal content={`[u]Plan[/u]: [r]2 to delete[/r]
118
119[r]-[/r] [b]Worker[/b] [d](Cloudflare.Worker)[/d]
120[r]-[/r] [b]Bucket[/b] [d](Cloudflare.R2.Bucket)[/d]
121
122Proceed?
123◉ Yes ○ No
124[r]✗[/r] [b]Worker[/b] [d](Cloudflare.Worker)[/d] [r]deleted[/r]
125[r]✗[/r] [b]Bucket[/b] [d](Cloudflare.R2.Bucket)[/d] [r]deleted[/r]`} />
126
127## Removal policy
128
129Each resource carries a **removal policy** that decides what happens
130to the physical cloud object when the resource is deleted — orphaned,
131destroyed, or superseded as a replacement's old generation:
132
133| Policy | What the engine does |
134| ----------- | -------------------------------------------------------- |
135| `destroy` | Calls `provider.delete`, then drops the state row |
136| `retain` | Skips `provider.delete`, then drops the state row |
137
138Under `retain`, alchemy forgets the resource either way — only the
139cloud object survives. Most resource types default to `destroy`; a few
140whose contents are irreplaceable default to `retain` (e.g.
141[`GitHub.Repository`](/github/repository), `Cloudflare.Zone`).
142
143Set the policy by piping a declaration through `retain()` or
144`destroy()`. It applies to every resource declared inside the piped
145effect, so it can decorate one resource or a whole scope:
146
147```typescript
148import * as RemovalPolicy from "alchemy/RemovalPolicy";
149
150Effect.gen(function* () {
151 const stack = yield* Stack;
152
153 // never deleted by alchemy
154 const uploads = yield* R2.Bucket("Uploads").pipe(RemovalPolicy.retain());
155
156 // retained in prod, torn down in every other stage
157 const cache = yield* R2.Bucket("Cache").pipe(
158 RemovalPolicy.retain(stack.stage === "prod"),
159 );
160});
161```
162
163The policy is a decoration, not a prop, so changing it produces no
164diff — the resource plans as a **no-op** and the plan reports no
165changes. The deploy still persists the new policy onto the state row
166(the orphan delete reads it from there, long after the declaration is
167gone), so a policy change takes effect from the very next deploy.
168
169:::caution
170`retain` protects the resource from *alchemy*, not from the provider's
171own delete semantics. Deleting a resource still cascades however that
172cloud API cascades — e.g. deleting a bucket that a provider empties
173first destroys its objects. Retain a resource **before** the deploy
174that would remove it, and prefer moving irreplaceable data behind a
175retained resource.
176:::
177
178## Idempotency and recovery
179
180State persistence can fail after the cloud operation succeeds — the
181network drops between "bucket created" and "state saved". Alchemy
182handles this by requiring `reconcile` and `delete` to be safe to
183retry:
184
185- **Reconcile**: deterministic physical names plus the observe-step
186 mean a retry finds the existing resource instead of creating a
187 duplicate, and re-syncs any aspect that drifted.
188- **Delete**: a missing resource is treated as already deleted.
189- **Read**: providers can implement `read` so alchemy can recover
190 state from the live cloud when persistence fails partway, and to
191 detect adoptable resources on a fresh state store.
192
193## Adoption
194
195When planning a resource that has no prior state, the engine calls
196`provider.read` (if the provider implements it). This serves two
197overlapping purposes:
198
199- **State recovery** — the resource was created on a previous
200 deploy, but state was lost between the cloud op succeeding and the
201 store persisting. `read` finds the live resource and the engine
202 rebuilds `created` state from its attributes.
203- **Adoption** — you're deploying against existing infrastructure
204 you didn't manage with Alchemy yet (or you wiped state
205 intentionally). `read` recognizes the resource and the engine
206 imports it into the new state.
207
208A resource `read` recognizes as ours is adopted silently. One that
209exists but *isn't* ours fails with `OwnedBySomeoneElse` — re-running
210with `--adopt` (or scoping the effect with `adopt(true)`) unlocks
211the takeover. How providers signal ownership through `read`'s
212return value is part of the provider contract — see
213[Provider › read](/infrastructure-as-code/provider#read). For the CLI flag, see
214[Adopting Resources](/cli/adopting-resources).
215
216## Errors
217
218- **Retryable errors** (eventual consistency, dependency races) are
219 retried automatically with backoff.
220- **Non-retryable errors** (validation, authorization) fail
221 immediately and surface in the plan output.
222- **Partial failures** are safe to re-run thanks to idempotency.
223
224## Driving the lifecycle from the CLI
225
226The same engine powers all of these commands:
227
228| Command | What it does |
229| ------------------ | --------------------------------------------- |
230| `alchemy plan` | Run plan, print diff, exit |
231| `alchemy deploy` | Plan, prompt for approval, apply |
232| `alchemy destroy` | Plan with everything marked deleted, apply |
233| `alchemy dev` | Plan + apply continuously on file changes |
234
235See the [CLI reference](/cli) for the full set of flags
236(`--yes`, `--force`, `--dry-run`, `--stage`, `--profile`, ...).
237
238Every lifecycle operation on this page is implemented per resource
239type by a [Provider](/infrastructure-as-code/provider).
240
241## Where next
242
243- [Renaming Resources](/infrastructure-as-code/renaming) — migrate state
244 across a logical ID change instead of replacing. Next page.
245- [Providers](/infrastructure-as-code/provider) — the object that implements
246 `reconcile`, `delete`, `diff`, and `read` for a resource type.
247- [CLI](/cli) — the commands and flags that drive the
248 lifecycle.
249- [State Store](/state-store) — where the persisted state
250 behind the plan lives.