Skip to content

Local development

alchemy dev runs your stack on your machine. Your compute runs locally (Workers in workerd, Lambda and ECS in Docker containers), the services around it are emulated, and code changes hot reload in milliseconds. Alchemy.remote() runs any resource against the real cloud when you need it.

For what each cloud emulates and how, see the dedicated guides:

  • Cloudflare local development — Workers in workerd; simulators for KV, R2, D1, Queues, Hyperdrive, Workflows, Containers, and the Worker binding surface (browser rendering, images, email, cron, Stream, …).
  • AWS local development — Lambda and ECS in Docker containers with close to 40 emulated services (S3, DynamoDB, SQS, SNS, EventBridge, API Gateway, …), no AWS account required.
Terminal window
alchemy dev
✓ Bucket (Cloudflare.R2.Bucket) created (local)
✓ DB (Cloudflare.D1.Database) created (local)
✓ Worker (Cloudflare.Worker) created (local → workerd)
  • http://localhost:1337

Watching for changes ...

Three things happen:

  1. Emulatable services run locally — resources whose provider ships a local implementation are created as local simulators with dev:-prefixed ids. No cloud calls.
  2. Your code runs locally — Workers execute in workerd, Lambda functions and ECS tasks in Docker containers, websites on their framework’s own dev server — with bindings wired to the local simulators.
  3. File changes hot reload — edit your code and the running compute swaps in the new code without a redeploy.

Resources whose provider has no local implementation deploy to the real cloud, into the dev_$USER stage by default — separate from alchemy deploy’s live_$USER stage, so a bare alchemy dev cannot replace a stage you deployed. A stack that mixes emulated and live-only resources just works. See Dev vs deploy for tearing a dev stage down.

Watching for changes ...

↻ src/worker.ts changed
  • Rebuilding worker ...
✓ Worker reloaded in 42ms

↻ src/api.ts changed
  • Rebuilding worker ...
✓ Worker reloaded in 38ms
  • Code changes take effect in milliseconds, not minutes
  • Resources stay running across reloads — only your application code is rebuilt

Arbitrary dev processes — Vite, Next, anything with a dev server — join the loop via Command.Dev: started by alchemy dev, restarted when its inputs change, and a no-op on deploy.

In dev, emulatable resources are simulators. A local resource’s id is dev:-prefixed, which doubles as proof that no cloud call ran.

The local simulators reach beyond your compute’s bindings. Node-side capability clients follow the same rule: a seed script in an Action that writes to a dev: KV Namespace or S3 bucket lands in the same local simulator your Worker or Lambda reads.

Every other resource runs live in dev automatically, and Alchemy.remote() (below) runs an emulatable resource against the real cloud when local fidelity isn’t enough.

Because your code runs locally, you can attach a debugger:

  • Set breakpoints in your IDE
  • Inspect variables and call stacks
  • Profile performance

Alchemy.remote() opts a resource out of local emulation — “run this live even in dev”:

import * as Alchemy from "alchemy";
const worker = yield* Worker("Api", { ... }).pipe(Alchemy.remote());

During alchemy deploy the pin is a no-op — everything is live anyway. If a remote resource needs credentials you don’t have, alchemy dev tells you before touching anything.

yield* Effect.gen(function* () {
const api = yield* Worker("Api", { ... });
const site = yield* Worker("Site", { ... });
}).pipe(Alchemy.remote());

The policy is ambient and captured at registration (like adopt()), so everything inside the scope runs live. Resources with no local emulation already run live in dev — remote() on them is a no-op.

Local and live compose into hybrid topologies — a local Worker producing into a live queue whose local consumer drains it, or a local Lambda reading a live Secrets Manager secret. See the per-cloud guides for examples.

When a resource moves between local and live — you deploy after a dev session, or add/remove remote() — it’s planned as a replacement: the new instance is created live, and the local one (the workerd instance, the Docker container, the dev process) is torn down. Dev state never silently becomes cloud state.

alchemy dev and alchemy deploy run the same stack into different stages. Without --stage, dev uses dev_$USER and deploy uses live_$USER, so a dev session never touches what you deployed.

Terminal window
alchemy dev # stage dev_sam
alchemy deploy # stage live_sam

Every other command (destroy, plan, logs, drift) defaults to the deploy stage. Pass --stage to point one at a dev stage.

Terminal window
alchemy destroy # destroys live_sam
alchemy destroy --stage dev_sam # destroys your dev stage

A dev stage is worth destroying. Resources with no local emulator, and anything you pinned with remote(), are real cloud resources in that stage. They keep running after you stop alchemy dev.

Every checkout on your machine shares dev_$USER, so two worktrees running alchemy dev fight over one stage. See Worktrees to give each worktree its own dev, deploy, and test stages.

The test harness runs the same suite locally or live. Leave dev out of Test.make and the ALCHEMY_DEV env var picks the mode:

Terminal window
ALCHEMY_DEV=1 pnpm test # local emulators, no cloud calls
pnpm test # real cloud

Iterate with the local run, and run live when you need the real thing. A common split is local on pull requests and live on main:

.github/workflows/test.yml
- run: pnpm test
env:
ALCHEMY_DEV: ${{ github.ref == 'refs/heads/main' && 'false' || 'true' }}

See Test harness → dev for the option itself.

  • Cloudflare local development — what runs in workerd and which bindings are simulated.
  • AWS local development — the emulated AWS surface, Lambda/ECS containers, and zero-credential dev.
  • CI — deploy the same stack from GitHub Actions with PR previews.
  • Stages — how live_$USER, dev_$USER, pr-42, and prod stay isolated.
  • Dev servers — run any framework dev server as a Command.Dev resource in the dev loop.
  • Testing — run the same test suite against the local emulators with Test.make({ dev: true }).
  • Local Providers — build the local implementation of a resource.