Skip to content

Part 1: Your First Stack

In this first part you’ll install Alchemy and Effect, create a Stack with an AWS S3 Bucket, and deploy it — all in under five minutes.

  • Bun (recommended) or Node.js 22+
  • An AWS account and an IAM identity with permission to create the resources you plan to deploy

Start with an empty directory and initialize a package.json:

Terminal window
mkdir my-app && cd my-app && bun init -y

Install alchemy@next and effect@>=4.0.0-beta.93|| >=4.0.0:

Terminal window
bun add "alchemy@next" "effect@>=4.0.0-beta.93|| >=4.0.0" "@effect/platform-bun@>=4.0.0-beta.93|| >=4.0.0" "@effect/platform-node@>=4.0.0-beta.93|| >=4.0.0"

Every Alchemy program starts with a Stack — a collection of Resources managed by Providers with state tracked between deploys.

Create an alchemy.run.ts file:

alchemy.run.ts
import * as Alchemy from "alchemy";
import * as AWS from "alchemy/AWS";
import * as Effect from "effect/Effect";
export default Alchemy.Stack(
"MyApp",
{
providers: AWS.providers(),
state: AWS.state(),
},
Effect.gen(function* () {
// we'll add resources here next
}),
);

Alchemy.Stack is the root of every app. The first argument ("MyApp") doubles as a logical id and as the prefix Alchemy uses when AWS asks for physical resource names.

providers: AWS.providers() registers every AWS resource and IAM policy binding that ships with Alchemy, and resolves credentials from your Alchemy profile — SSO, environment variables, or stored access keys. You’ll pick one interactively the first time you deploy; see Setup for the details of each method.

state: AWS.state() stores deploy state in an account-regional S3 bucket (alchemy-state-{accountId}-{region}-an), created lazily on the first deploy. Because the state lives in your AWS account rather than on disk, the same configuration works locally and in CI with no extra setup.

Resources represent cloud infrastructure — buckets, tables, queues, functions, and so on. Each resource is yield*-ed inside the Stack’s Effect generator.

Add an S3 Bucket:

alchemy.run.ts
import * as Alchemy from "alchemy";
import * as AWS from "alchemy/AWS";
import * as Effect from "effect/Effect";
export default Alchemy.Stack(
"MyApp",
{
providers: AWS.providers(),
state: AWS.state(),
},
Effect.gen(function* () {
const bucket = yield* AWS.S3.Bucket("Bucket");
}),
);

yield* AWS.S3.Bucket("Bucket") registers the resource with the Stack under the logical id Bucket and returns a typed Bucket handle. We didn’t pass a bucketName, so Alchemy generates a unique physical name from the app, stage, and logical id.

Stack outputs let you see important values after a deploy. Return an object from the generator to expose them:

Effect.gen(function* () {
const bucket = yield* AWS.S3.Bucket("Bucket");
return {
bucketName: bucket.bucketName,
};
}),

bucket.bucketName is an Output — a typed reference to an attribute the cloud will produce. It resolves during deploy and prints with the rest of the stack outputs.

Run alchemy deploy to create the Bucket on AWS:

Terminal window
bun alchemy deploy

The first time you deploy, Alchemy prompts you to pick an AWS authentication method — SSO (recommended), environment variables, or stored access keys — and saves the choice to your default profile. The region and account come from whichever method you pick. See Setup for a walkthrough of all three.

Plan: 1 to create
+ Bucket (AWS.S3.Bucket)

Proceed?
◉ Yes ○ No
 Bucket (AWS.S3.Bucket) created
{
  bucketName: "myapp-bucket-a1b2c3d4e5",
}

Alchemy shows a plan, asks for confirmation, creates the resource, and prints the stack outputs. Your bucket is live on AWS.

Your newly created bucket will be listed in the S3 section of the AWS Console (or via aws s3 ls if you have the AWS CLI installed).

Run alchemy deploy again. Because nothing changed, the bucket shows as a no-op:

Plan: no changes

{
  bucketName: "myapp-bucket-a1b2c3d4e5",
}

This is the core loop — declare resources in code, deploy, and Alchemy figures out what changed.

The inverse of deploy is destroy — it plans against an empty desired state and deletes everything the stack created:

Terminal window
bun alchemy destroy

Try it if you like, then run bun alchemy deploy again to bring the bucket back — we’ll build on it in Part 2.

You now have:

  • An alchemy.run.ts with a Stack and an AWS S3 Bucket
  • A live bucket deployed to your AWS account
  • Stack outputs showing the bucket name

In Part 2, you’ll add a Lambda Function with a public URL that reads and writes objects in this bucket.