Stacks
A Stack is the top-level unit of deployment in Alchemy. It groups resources together, wires up providers, and tracks state across deploys.
Defining a Stack
Section titled “Defining a Stack”A Stack is just an Effect that you export default from a TypeScript
file:
import * as Alchemy from "alchemy";import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";
export default Alchemy.Stack( "MyApp", { providers: Cloudflare.providers(), state: Cloudflare.state() }, Effect.gen(function* () { const bucket = yield* Cloudflare.R2.Bucket("Bucket"); return { bucketName: bucket.bucketName }; }),);Alchemy.Stack takes three arguments:
- Name — identifies this stack in state storage
- Options —
providersandstate, both required (see State Store for the available state layers) - Effect — a generator that declares resources and returns outputs
Multiple providers
Section titled “Multiple providers”A stack is not limited to one cloud. Providers are Effect Layers, so
to deploy resources from several providers in a single stack, merge
their layers with Layer.mergeAll:
import * as Alchemy from "alchemy";import * as AWS from "alchemy/AWS";import * as Cloudflare from "alchemy/Cloudflare";import * as Neon from "alchemy/Neon";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";
export default Alchemy.Stack( "MyApp", { providers: Layer.mergeAll( Cloudflare.providers(), AWS.providers(), Neon.providers(), ), state: Cloudflare.state(), }, Effect.gen(function* () { const db = yield* Neon.Project("Db"); const queue = yield* AWS.SQS.Queue("Jobs"); const bucket = yield* Cloudflare.R2.Bucket("Uploads"); return { projectId: db.projectId }; }),);The type system tracks which providers the stack’s resources need —
declaring an AWS resource without AWS.providers() in the merged
layer is a compile-time error.
state is independent of providers: pick one state layer no
matter how many clouds the stack deploys to.
See Providers for more on provider layers.
Stack outputs
Section titled “Stack outputs”The value returned from the generator becomes the stack output. After a deploy, outputs are printed to the console and available programmatically in tests.
Effect.gen(function* () { const bucket = yield* Cloudflare.R2.Bucket("Bucket"); const worker = yield* Worker;
return { bucketName: bucket.bucketName, url: worker.url, };});The CLI renders them after every successful deploy:
Stages
Section titled “Stages”Every deploy targets a stage — an isolated instance of the stack
like dev_sam, staging, or prod. The stage defaults to
dev_$USER, so each developer gets their own environment
automatically. State and physical names are namespaced by stage.
# Each stage = its own state file + its own physical names.$ alchemy deploy --stage dev_sam # -> myapp-dev_sam-photos-a3f1$ alchemy deploy --stage pr-147 # -> myapp-pr_147-photos-9b2c$ alchemy deploy --stage prod # -> myapp-prod-photos-7d4e
# Three independent deployments. Destroying one# never touches the others.For naming patterns, isolation, and per-stage configuration see Stages. Credentials per environment are managed via Profiles.
Accessing the Stack
Section titled “Accessing the Stack”Inside a resource or layer, you can access the current Stack’s
metadata via the Stack service:
import { Stack } from "alchemy/Stack";import * as Console from "effect/Console";
Effect.gen(function* () { const stack = yield* Stack; yield* Console.log(stack.name); // "MyApp" yield* Console.log(stack.stage); // "dev_sam"
const queue = yield* SQS.Queue("Jobs").pipe( RemovalPolicy.retain(stack.stage === "prod"), );});Stack.useSync for module-scope resources
Section titled “Stack.useSync for module-scope resources”When you declare a resource at module scope (outside any
Effect.gen), there is no yield* available to read the Stack
service. But a Resource’s props can themselves be an Effect —
Stack.useSync builds one that computes props from the current
stack, useful for parameterizing names by stage:
import * as Axiom from "alchemy/Axiom";import { Stack } from "alchemy/Stack";
export const Traces = Axiom.Dataset( "Traces", Stack.useSync(({ stage }) => ({ name: `${stage}-traces`, kind: "otel:traces:v1" as const, description: `OTEL traces for stage '${stage}'`, retentionDays: 30, })),);The function runs once at plan time, after the stage is resolved.
Use it anywhere a resource accepts a props object.
Cross-stack references
Section titled “Cross-stack references”A typed Alchemy.Stack handle lets one stack read another’s
persisted outputs at plan time:
// backend/src/Stack.ts — typed handle shared across packagesexport class Backend extends Alchemy.Stack< Backend, { url: string }>()("Backend") {}// frontend/alchemy.run.ts — reads the matching stage of Backendimport { Backend } from "backend";
export default Alchemy.Stack( "Frontend", { providers: Cloudflare.providers(), state: Cloudflare.state(), }, Effect.gen(function* () { const backend = yield* Backend;
const website = yield* Cloudflare.Website.Vite("Website", { env: { VITE_API_URL: backend.url }, });
return { url: website.url.as<string>() }; }),);yield* Backend defaults to the current stage — sam
frontend reads sam backend, pr-42 reads pr-42. Use
Backend.stage.<name> to pin to a specific stage instead.
See References for the underlying operators and Monorepo for the end-to-end walkthrough (package layout, schema sharing, deploy ordering).
A stack is just the container — the things it deploys are Resources.