Skip to content

Infrastructure as Effects

Your infrastructure and application logic in a single, type-safe program.

Alchemy extends Infrastructure-as-Code with Effects — your cloud resources and the code that uses them are defined together in a single type-safe program.

No YAML. No separate config files. No runtime wiring. If it compiles, it deploys.

alchemy.run.ts
const Bucket = Cloudflare.R2Bucket("Bucket");
export default Alchemy.Stack(
"MyApp",
{ providers: Cloudflare.providers() },
Effect.gen(function* () {
const bucket = yield* Bucket;
const worker = yield* Worker;
return { url: worker.url };
}),
);

Type-safe from cloud to code

The type system ensures every resource has its provider, every binding is wired correctly, and every dependency is satisfied — before you deploy.

alchemy.run.ts
Alchemy.Stack(
"App",
{
// Type Error: Layer<never> is not assignable
// to Layer<Cloudflare.Providers>
providers: Layer.empty,
},
Effect.gen(function* () {
yield* Cloudflare.R2Bucket("Bucket");
}),
);

Resources are just Effects

Resources are declared as Effects and composed with yield*. Import them from any file, bind them to Workers, pass their outputs to other resources — it’s all just TypeScript.

src/bucket.ts
export const Bucket = Cloudflare.R2Bucket("Bucket");
// src/worker.ts
import { Bucket } from "./bucket.ts";
export default Cloudflare.Worker("Worker",
{ main: import.meta.path },
Effect.gen(function* () {
const bucket = yield* Cloudflare.R2Bucket.bind(Bucket);
return { fetch: /* ... */ };
}),
);

Test against real infrastructure

Deploy your stack in beforeAll, make HTTP requests against live endpoints, and tear it down in afterAll. No mocks, no simulators — real integration tests.

test/integ.test.ts
const stack = beforeAll(deploy(Stack));
afterAll.skipIf(!process.env.CI)(destroy(Stack));
test(
"round-trip an object",
Effect.gen(function* () {
const { url } = yield* stack;
yield* HttpClient.put(`${url}/hello.txt`, {
body: HttpBody.text("Hello!"),
});
const res = yield* HttpClient.get(`${url}/hello.txt`);
expect(yield* res.text).toBe("Hello!");
}),
);

Local dev with hot reload

Run your entire stack locally with a single command. Workers run locally in workerd, and changes hot reload instantly.

$ alchemy dev

 Bucket (Cloudflare.R2Bucket) created (local)
 Worker (Cloudflare.Worker) created (local)
  • http://localhost:1337

Watching for changes ...

Plan, deploy, destroy

Preview what will change with plan, apply it with deploy, and tear it down with destroy. Stages isolate environments so dev and prod never collide.

$ alchemy deploy --stage prod

Plan: 2 to create

+ Bucket (Cloudflare.R2Bucket)
+ Worker (Cloudflare.Worker) (1 bindings)
  + Bucket

Proceed?
◉ Yes ○ No
 Bucket (Cloudflare.R2Bucket) created
 Worker (Cloudflare.Worker) created

CI/CD out of the box

Push to main to deploy prod. Open a PR and get an isolated preview environment. Merge and it’s cleaned up automatically. Alchemy even posts the preview URL as a PR comment.

.github/workflows/deploy.yml
jobs:
deploy:
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun alchemy deploy --stage ${{ env.STAGE }}
cleanup:
if: github.event.action == 'closed'
steps:
- run: bun alchemy destroy --stage ${{ env.STAGE }}

Alchemy supports multiple cloud providers with more being added. Each provider is a typed Layer that the compiler verifies at build time.

Cloudflare

Workers, R2 Buckets, KV, D1, Durable Objects, Queues, Vectorize, AI

AWS

Lambda, S3, DynamoDB, SQS, Kinesis, IAM, EC2, API Gateway

More

GitHub, Stripe, DNS, and a growing ecosystem of community providers

Follow the tutorial to go from zero to a deployed Cloudflare Worker with R2 bindings, integration tests, local dev, and CI/CD — in under 30 minutes.