1---
2title: "Part 1: Your First Stack"
3description: Install Alchemy, create a Stack with a Cloudflare R2 Bucket, and deploy it.
4sidebar:
5 order: 1
6---
7
8import Terminal from "../../../../components/Terminal.astro";
9import StateStoreBootstrap from "../../../../components/marketing-islands/StateStoreBootstrap.tsx";
10import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
11import { effectVersion } from "../../../../versions";
12
13export const pkgs = `"alchemy@latest" "effect@${effectVersion}" "@effect/platform-bun@${effectVersion}" "@effect/platform-node@${effectVersion}"`;
14
15In this first part you'll install Alchemy and Effect, create a Stack
16with a Cloudflare R2 Bucket, and deploy it, all in under five minutes.
17
18:::tip
19Already deployed the Bucket from [Getting started](/getting-started)?
20That was this part. Skip to [Part 2](/cloudflare/tutorial/part-2).
21:::
22
23## Prerequisites
24
25- [Bun](https://bun.sh) or Node.js 22+
26- A [Cloudflare](https://dash.cloudflare.com/sign-up) account
27
28## Create a project
29
30Start with an empty directory and initialize a `package.json`:
31
32<Tabs syncKey="pkgManager">
33 <TabItem label="bun" icon="bun">
34 <Code code="mkdir my-app && cd my-app && bun init -y" lang="sh" />
35 </TabItem>
36 <TabItem label="npm" icon="npm">
37 <Code code="mkdir my-app && cd my-app && npm init -y" lang="sh" />
38 </TabItem>
39 <TabItem label="pnpm" icon="pnpm">
40 <Code code="mkdir my-app && cd my-app && pnpm init" lang="sh" />
41 </TabItem>
42 <TabItem label="yarn" icon="seti:yarn">
43 <Code code="mkdir my-app && cd my-app && yarn init -y" lang="sh" />
44 </TabItem>
45</Tabs>
46
47## Install dependencies
48
49Install <code>alchemy@latest</code> and <code>effect@{effectVersion}</code>:
50
51<Tabs syncKey="pkgManager">
52 <TabItem label="bun" icon="bun">
53 <Code code={`bun add ${pkgs}`} lang="sh" />
54 </TabItem>
55 <TabItem label="npm" icon="npm">
56 <Code code={`npm install ${pkgs}`} lang="sh" />
57 </TabItem>
58 <TabItem label="pnpm" icon="pnpm">
59 <Code code={`pnpm add ${pkgs}`} lang="sh" />
60 </TabItem>
61 <TabItem label="yarn" icon="seti:yarn">
62 <Code code={`yarn add ${pkgs}`} lang="sh" />
63 </TabItem>
64</Tabs>
65
66## Create the Stack
67
68Every Alchemy program starts with a `Stack` — a collection of
69Resources managed by Providers with state tracked between deploys.
70
71Create an `alchemy.run.ts` file:
72
73```typescript
74// alchemy.run.ts
75import * as Alchemy from "alchemy";
76import * as Effect from "effect/Effect";
77import * as Layer from "effect/Layer";
78
79export default Alchemy.Stack(
80 "MyApp",
81 {
82 // @error: ts(2345) Argument of type '{ providers: Layer.Layer<never, never, never>; }' is not assignable to parameter of type 'StackProps<never>'.
83 // @error: Property 'state' is missing in type '{ providers: Layer.Layer<never, never, never>; }' but required in type 'StackProps<never>'.
84 providers: Layer.empty,
85 },
86 Effect.gen(function* () {
87 // we'll add resources here next
88 }),
89);
90```
91
92TypeScript is unhappy: the `state` property is required. Every Stack
93needs a **state store** so Alchemy can persist resource state between
94deploys and compute diffs against your infrastructure.
95
96## Configure state
97
98For this tutorial we'll use `Cloudflare.state()`, which persists state
99in a Cloudflare-hosted Worker backed by a Durable Object:
100
101```diff lang="typescript"
102// alchemy.run.ts
103import * as Alchemy from "alchemy";
104+import * as Cloudflare from "alchemy/Cloudflare";
105import * as Effect from "effect/Effect";
106import * as Layer from "effect/Layer";
107
108export default Alchemy.Stack(
109 "MyApp",
110 {
111 providers: Layer.empty,
112+ state: Cloudflare.state(),
113 },
114 Effect.gen(function* () {
115 // we'll add resources here next
116 }),
117);
118```
119
120The first time you run `alchemy deploy`, `plan`, or `dev`, Alchemy
121will prompt for permission and bootstrap the state-store Worker into
122your Cloudflare account. It looks like this:
123
124<StateStoreBootstrap client:visible />
125
126This is a one-time event — the state-store Worker, its Durable
127Object, and the Secrets Store entries holding its auth token and
128encryption key are reused across every stack and stage on this
129Cloudflare account. See [State Store](/state-store) for
130the full picture.
131
132:::note[Other state stores]
133Don't want to deploy a Worker just for state? Swap `Cloudflare.state()`
134for `State.localState()` from `alchemy/State` to keep state on disk
135under `.alchemy/`, or [write your own](/state-store).
136:::
137
138## Add a Resource
139
140Resources represent cloud infrastructure — buckets, queues, functions,
141databases, and so on. Each resource is `yield*`-ed inside the Stack's
142Effect generator.
143
144Let's add a Cloudflare R2 Bucket to our Stack and observe the type error:
145
146```diff lang="typescript"
147// alchemy.run.ts
148import * as Alchemy from "alchemy";
149import * as Cloudflare from "alchemy/Cloudflare";
150import * as Effect from "effect/Effect";
151import * as Layer from "effect/Layer";
152
153export default Alchemy.Stack(
154 "MyApp",
155 {
156 providers: Layer.empty,
157 // @error: ts(2322) Type 'Layer<never, never, never>' is not assignable to type 'Layer<NoInfer<Providers>, never, StackServices>'.
158 // @error: Type 'Providers' is not assignable to type 'never'.
159 state: Cloudflare.state(),
160 },
161 Effect.gen(function* () {
162+ const bucket = yield* Cloudflare.R2.Bucket("Bucket");
163 }),
164);
165```
166
167TypeScript is telling us that `Layer.empty` doesn't provide
168`Cloudflare.Providers` — the layer required by `Bucket`.
169
170## Fix the Providers
171
172Replace `Layer.empty` with `Cloudflare.providers()` to resolve the
173type error:
174
175```diff lang="typescript"
176// alchemy.run.ts
177import * as Alchemy from "alchemy";
178import * as Cloudflare from "alchemy/Cloudflare";
179import * as Effect from "effect/Effect";
180-import * as Layer from "effect/Layer";
181
182export default Alchemy.Stack(
183 "MyApp",
184 {
185+ providers: Cloudflare.providers(),
186 state: Cloudflare.state(),
187 },
188 Effect.gen(function* () {
189 const bucket = yield* Cloudflare.R2.Bucket("Bucket");
190 }),
191);
192```
193
194Now the program type-checks. The providers layer tells Alchemy how to
195talk to Cloudflare's APIs, and the type system ensures you never
196forget to wire it up.
197
198## Return Stack outputs
199
200Stack outputs let you see important values after a deploy. Return an
201object from the generator to expose them:
202
203```diff lang="typescript"
204// alchemy.run.ts
205import * as Alchemy from "alchemy";
206import * as Cloudflare from "alchemy/Cloudflare";
207import * as Effect from "effect/Effect";
208
209export default Alchemy.Stack(
210 "MyApp",
211 {
212 providers: Cloudflare.providers(),
213 state: Cloudflare.state(),
214 },
215 Effect.gen(function* () {
216 const bucket = yield* Cloudflare.R2.Bucket("Bucket");
217
218+ return {
219+ bucketName: bucket.bucketName,
220 };
221 }),
222);
223```
224
225## Deploy
226
227Run `alchemy deploy` to create the Bucket on Cloudflare:
228
229<Tabs syncKey="pkgManager">
230 <TabItem label="bun" icon="bun">
231 <Code code="bun alchemy deploy" lang="sh" />
232 </TabItem>
233 <TabItem label="npm" icon="npm">
234 <Code code="npm run alchemy deploy" lang="sh" />
235 </TabItem>
236 <TabItem label="pnpm" icon="pnpm">
237 <Code code="pnpm alchemy deploy" lang="sh" />
238 </TabItem>
239 <TabItem label="yarn" icon="seti:yarn">
240 <Code code="yarn alchemy deploy" lang="sh" />
241 </TabItem>
242</Tabs>
243
244If you haven't connected Cloudflare yet, the deploy fails with the exact
245`alchemy profile edit` command to run. Use the `profile` command to sign in
246and save credentials to your
247**`default`** [profile](/environments/profiles) at
248`~/.alchemy/profiles.json`. For Cloudflare you can sign in with
249OAuth in the browser or paste an API token — no environment
250variables or `wrangler login` required.
251
252:::tip
253To re-run the credential prompt later (e.g. to switch from OAuth to
254an API token), use `alchemy profile edit --reconfigure Cloudflare`.
255For a separate profile, run `alchemy profile create prod` followed by
256`alchemy profile edit --profile prod`.
257:::
258
259<Terminal content={`[u]Plan[/u]: [g]1 to create[/g]
260[g]+[/g] [b]Bucket[/b] [d](Cloudflare.R2.Bucket)[/d]
261
262Proceed?
263◉ Yes ○ No
264[g]✓[/g] [b]Bucket[/b] [d](Cloudflare.R2.Bucket)[/d] [g]created[/g]
265{
266[s2]bucketName: "myapp-bucket-a1b2c3d4e5",
267}`} />
268
269Alchemy shows a plan, asks for confirmation, creates the resource,
270and prints the stack outputs. Your bucket is live on Cloudflare.
271
272:::tip
273By convention, Alchemy looks for `alchemy.run.ts` at the root of your
274project. You can specify a different file with
275`alchemy deploy --config <file>`.
276:::
277
278## Verify it worked
279
280Your newly created R2 bucket will be listed on the [Cloudflare R2 Object Storage Dashboard](https://dash.cloudflare.com/?to=/:account/r2/overview).
281
282Run `alchemy deploy` again. Because nothing changed, the bucket shows
283as a no-op:
284
285<Terminal content={`[u]Plan[/u]: [d]no changes[/d]
286
287{
288[s2]bucketName: "myapp-bucket-a1b2c3d4e5",
289}`} />
290
291This is the core loop — declare resources in code, deploy, and
292Alchemy figures out what changed.
293
294## Recap
295
296You now have:
297
298- An `alchemy.run.ts` with a Stack and a Cloudflare R2 Bucket
299- A live bucket deployed to your Cloudflare account
300- Stack outputs showing the bucket name
301
302In [Part 2](/cloudflare/tutorial/part-2), you'll add a Cloudflare Worker that
303uses this bucket to serve HTTP requests.