Infrastructure as Effects
One program. Infrastructure and logic.
Section titled “One program. Infrastructure and logic.”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.
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.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.
export const Bucket = Cloudflare.R2Bucket("Bucket");
// src/worker.tsimport { 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.
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.
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 }}Cloud providers
Section titled “Cloud providers”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
Ready to start?
Section titled “Ready to start?”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.