Part 1: Your First App
Install Alchemy and Effect, create a Stack with a Fly App, and deploy it. An App is the parent for Machines, Services, Secrets, and disks.
Prerequisites
Section titled “Prerequisites”- Bun (recommended) or Node.js 22+
- A Fly organization and an API token — see Setup if you haven’t created those yet
Create a project
Section titled “Create a project”Start with an empty directory and initialize a package.json:
mkdir my-app && cd my-app && bun init -ymkdir my-app && cd my-app && npm init -ymkdir my-app && cd my-app && pnpm initmkdir my-app && cd my-app && yarn init -yInstall dependencies
Section titled “Install dependencies”Install alchemy@latest and effect@rc:
bun add "alchemy@latest" "effect@rc" "@effect/platform-bun@rc" "@effect/platform-node@rc"npm install "alchemy@latest" "effect@rc" "@effect/platform-bun@rc" "@effect/platform-node@rc"pnpm add "alchemy@latest" "effect@rc" "@effect/platform-bun@rc" "@effect/platform-node@rc"yarn add "alchemy@latest" "effect@rc" "@effect/platform-bun@rc" "@effect/platform-node@rc"Create the Stack
Section titled “Create the Stack”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:
import * as Alchemy from "alchemy";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";
export default Alchemy.Stack( "MyApp", {Error ts(2345) ― providers: Layer.empty, }, Effect.gen(function* () { // we'll add resources here next }),);TypeScript is unhappy: the state property is required. Every Stack
needs a state store so Alchemy can persist resource state between
deploys and compute diffs against your infrastructure.
Configure state
Section titled “Configure state”Fly has no state backend of its own, so we’ll keep state on disk
with Alchemy.localState() — it writes to .alchemy/ next to your
code, no setup required:
import * as Alchemy from "alchemy";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";
export default Alchemy.Stack( "MyApp", { providers: Layer.empty, state: Alchemy.localState(), }, Effect.gen(function* () { // we'll add resources here next }),);Add an App
Section titled “Add an App”Resources represent cloud infrastructure. Each resource is
yield*-ed inside the Stack’s Effect generator.
Let’s declare a Fly App and observe the type error:
import * as Alchemy from "alchemy";import * as Fly from "alchemy/Fly";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";
export default Alchemy.Stack( "MyApp", { providers: Layer.empty,Error ts(2322) ― state: Alchemy.localState(), }, Effect.gen(function* () { const site = yield* Fly.App("Site"); }),);Omit name and Alchemy generates a globally unique, DNS-safe App
name (max 30 characters, leading letter). The org defaults to
whatever org your token belongs to.
TypeScript is telling us that Layer.empty doesn’t provide
Fly.Providers — the layer required by App.
Fix the Providers
Section titled “Fix the Providers”Replace Layer.empty with Fly.providers() to resolve the type
error:
import * as Alchemy from "alchemy";import * as Fly from "alchemy/Fly";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";
export default Alchemy.Stack( "MyApp", { providers: Fly.providers(), state: Alchemy.localState(), }, Effect.gen(function* () { const site = yield* Fly.App("Site"); }),);Now the program type-checks. The providers layer tells Alchemy how to talk to the Fly Machines API, and the type system ensures you never forget to wire it up.
Return Stack outputs
Section titled “Return Stack outputs”Stack outputs let you see important values after a deploy. Return an object from the generator to expose the App’s name and public URL:
Effect.gen(function* () { const site = yield* Fly.App("Site");
return { appName: site.appName, url: site.url, }; }),url is https://{appName}.fly.dev. Nothing is listening there yet
— that’s Part 2.
Deploy
Section titled “Deploy”Run alchemy deploy to create the App:
bun alchemy deploynpm run alchemy deploypnpm alchemy deployyarn alchemy deployThe first time you deploy, Alchemy prompts for Fly credentials —
paste the API token you generated in Setup. The token
is verified and saved to your default
profile, so you won’t be asked again.
Plan: 1 to create + Site (Fly.App) Proceed? ◉ Yes ○ No ✓ Site (Fly.App) created { appName: "myapp-site-dev-a1b2c3d4", url: "https://myapp-site-dev-a1b2c3d4.fly.dev", }
Alchemy shows a plan, asks for confirmation, creates the App, and prints the stack outputs.
Verify it worked
Section titled “Verify it worked”The App is listed in your org in the
Fly dashboard. Run alchemy deploy
again. Because nothing changed, the App shows as a no-op:
Plan: no changes { appName: "myapp-site-dev-a1b2c3d4", url: "https://myapp-site-dev-a1b2c3d4.fly.dev", }
This is the core loop — declare resources in code, deploy, and Alchemy figures out what changed.
You now have:
- An
alchemy.run.tswith a Stack and a Fly App - A live App in your org with a generated globally unique name
- Stack outputs showing
appNameandhttps://{appName}.fly.dev
In Part 2, you’ll deploy code to this App —
a Fly.Service that bundles an Effect HTTP handler into a Machine.