1---
2title: Local development
3description: alchemy dev runs Workers in workerd with local simulators for KV, R2, D1, Queues, Hyperdrive, Workflows, Containers, and the Worker binding surface — browser rendering, images, email, cron, Stream, and more.
4---
5
6import Terminal from "../../../components/Terminal.astro";
7
8`alchemy dev` runs your Cloudflare stack on your machine. Workers
9execute in workerd — the same runtime used in production — with
10local simulators behind their bindings, websites run their
11frameworks' own dev servers with native HMR, containers run in
12Docker, and code changes hot reload in milliseconds.
13
14```sh
15alchemy dev
16```
17
18<Terminal content={`[g]✓[/g] [b]Bucket[/b] [d](Cloudflare.R2.Bucket)[/d] [g]created[/g] [d](local)[/d]
19[g]✓[/g] [b]DB[/b] [d](Cloudflare.D1.Database)[/d] [g]created[/g] [d](local)[/d]
20[g]✓[/g] [b]Worker[/b] [d](Cloudflare.Worker)[/d] [g]created[/g] [d](local → workerd)[/d]
21[s2][d]• http://localhost:1337[/d]
22
23[d]Watching for changes ...[/d]`} />
24
25## Workers run in workerd
26
27Your Worker code executes in workerd with bindings wired to the
28local simulators, so binding behavior matches what you deploy:
29
30```typescript
31export const Cache = Cloudflare.KV.Namespace("Cache");
32
33export default class Api extends Cloudflare.Worker<Api>()(
34 "Api",
35 { main: import.meta.url },
36 Effect.gen(function* () {
37 const kv = yield* Cloudflare.KV.ReadWriteNamespace(Cache);
38 return {
39 fetch: Effect.gen(function* () {
40 yield* kv.put("greeting", "hello");
41 const value = yield* kv.get("greeting");
42 return yield* HttpServerResponse.text(value ?? "");
43 }).pipe(Effect.orDie),
44 };
45 }).pipe(Effect.provide(Cloudflare.KV.ReadWriteNamespaceBinding)),
46) {}
47```
48
49In dev, `kv.put` lands in a local KV simulator and the Worker
50serves at `http://localhost:<port>`. Durable Objects run inside
51the same workerd instance, cron triggers fire on their real
52schedule (and the miniflare-compatible
53`/cdn-cgi/handler/scheduled` route triggers them manually), and
54because everything is a local process you can attach a debugger,
55set breakpoints, and profile.
56
57A local resource's id is `dev:`-prefixed, which doubles as proof
58that no cloud call ran.
59
60## Websites run their own dev servers
61
62Every `Cloudflare.Website.*` resource — Vite, Next.js, Astro,
63Nuxt, SvelteKit, Waku, Octane, Foldkit, StaticSite — runs its
64framework's own dev server under `alchemy dev`: native HMR, no
65cloud resources. The site's `url` is the local dev server's
66address:
67
68```typescript
69const site = yield* Cloudflare.Website.Vite("Web");
70// dev: site.url = http://localhost:5173 — Vite's own dev server, HMR included
71// deploy: site.url = the deployed site
72```
73
74See
75[Frontend frameworks](/cloudflare/frontend/frontends).
76
77## Containers run in Docker
78
79A `Cloudflare.Container` runs as a real Docker container on your
80machine, driven by its Durable Object in the local workerd:
81
82```typescript
83export class Sandbox extends Cloudflare.Container<
84 Sandbox,
85 { ping: () => Effect.Effect<string> }
86>()("Sandbox") {}
87
88export default Sandbox.make(
89 { main: import.meta.url },
90 Effect.gen(function* () {
91 return Sandbox.of({
92 ping: () => Effect.succeed("pong"),
93 fetch: Effect.succeed(HttpServerResponse.text("hello")),
94 });
95 }),
96);
97```
98
99All three image sources work locally: `main` (a bundled Effect
100program), `context` (your own Dockerfile), and `image` (a
101pre-built remote image).
102
103## What's emulated
104
105These resources ship a local provider and run as simulators in
106dev:
107
108| Resource | Local behavior |
109| ------------------------ | --------------------------------------------------------------------------------- |
110| Workers | workerd, with hot reload and a `http://localhost:<port>` URL |
111| Durable Objects | run inside the local workerd, including SQLite storage |
112| KV Namespaces | local simulator; Node-side capability clients hit the same store |
113| R2 Buckets | local simulator; an S3-compatible endpoint serves [presigned URLs](/cloudflare/data/r2-presigned-urls) |
114| D1 Databases | local simulator; `migrations` and `importFiles` apply on every reconcile |
115| Queues + consumers | local broker with batching, retry, and dead-letter semantics |
116| Hyperdrive | connects directly to your database |
117| Workflows | run locally with real step semantics |
118| Containers | run as Docker containers on your machine |
119| Secrets Store | local store seeded with the real (Redacted-sourced) values |
120| Websites | the framework's own dev server (Vite, Astro, Next.js, Nuxt, SvelteKit, Waku, Octane, Foldkit, StaticSite) with native HMR |
121
122Worker-only bindings are simulated too:
123
124- **Email, both directions** — a `send_email` binding persists
125 messages as `.eml` files under `.alchemy/local/email`, and a
126 `POST` to `/cdn-cgi/handler/email` drives your `email()` handler
127 with accept, reject, and reply semantics.
128- **Browser Rendering** — `env.BROWSER` drives a locally-launched
129 headless Chrome over CDP; `@cloudflare/puppeteer` works
130 unchanged.
131- **Images** — `env.IMAGES` transforms run through sharp.
132- **Stream** — uploads land in a local video store.
133- **Tail consumers** — a local producer's trace events reach a
134 local tail Worker.
135- **Secret keys** — `secret_key` CryptoKey bindings boot locally.
136- **Access** — `dev: { access: { identity } }` stubs the
137 authenticated identity behind `ctx.access`, so gated paths are
138 testable without a login wall. See
139 [Protect a Worker with Access](/cloudflare/security/access).
140
141Resources whose provider has no local implementation (a Vectorize
142index, DNS records) deploy to the real cloud, into your personal
143[stage](/environments/stages), so a stack that mixes emulated and
144live-only resources just works.
145
146## Mix in the real cloud
147
148`Alchemy.remote()` opts any resource (or a whole scope) out of
149local emulation:
150
151```typescript
152// Effect-native Worker
153const browser = yield* Cloudflare.Browser("BROWSER").pipe(Alchemy.remote());
154
155// async Worker env
156env: { IMAGES: Cloudflare.Images.Images("IMAGES").pipe(Alchemy.remote()) }
157```
158
159Local and live compose into hybrid topologies. Pin a queue live
160and everything around it stays local — the local Worker produces
161real messages into the real queue, and its local consumer drains
162the same queue through a pull loop:
163
164```typescript
165// the queue is real, even in dev
166const Jobs = Cloudflare.Queues.Queue("Jobs").pipe(Alchemy.remote());
167```
168
169Your dev session sees the same traffic as staging — messages
170produced by deployed services or a teammate's session land in
171your local handler.
172
173Switching a resource between local and live (or dev → deploy)
174plans a **replacement** — dev state never silently becomes cloud
175state. See
176[Local development](/environments/local-development) for the
177shared semantics.
178
179## Custom port
180
181```typescript
182export default Cloudflare.Worker("Worker", {
183 main: import.meta.url,
184 dev: {
185 port: 3000,
186 },
187 // ...
188});
189```
190
191## Test against the local stack
192
193The [test harness](/testing/test-harness) has the same dev mode —
194`Test.make({ dev: true })` (available on both the Bun and Vitest
195adapters, off by default) runs the suite's `deploy(Stack)` against
196the local simulators instead of the cloud, no separate
197`alchemy dev` terminal needed:
198
199```typescript
200const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
201 providers: Cloudflare.providers(),
202 dev: true,
203});
204```
205
206Workers, Durable Objects, Workflows, and the simulated bindings all
207run in workerd exactly as described above, and the stack's `url`
208points at `http://localhost:<port>`. See
209[Test harness → dev](/testing/test-harness#dev) and
210[Tutorial Part 4](/cloudflare/tutorial/part-4).
211
212## Where next
213
214- [Local development](/environments/local-development) — the
215 shared `alchemy dev` concepts: hot reload, `Alchemy.remote()`,
216 and dev vs deploy semantics.
217- [Frontend frameworks](/cloudflare/frontend/frontends) —
218 framework dev servers under `alchemy dev`.
219- [Stages](/environments/stages) — how live-in-dev resources stay
220 isolated per developer.
221- [Testing](/testing) — run the same test suite against the local
222 simulators with `Test.make({ dev: true })`.
223- [Local Providers](/infrastructure-as-code/local-provider) —
224 build the local implementation of a resource.