1---
2title: "Part 1: Your First Service"
3description: Install Alchemy, create a Stack, and deploy an Effect HTTP Service to Fly on its own fly.dev hostname.
4sidebar:
5 order: 1
6---
7
8import Terminal from "../../../../components/Terminal.astro";
9import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
10import { effectVersion } from "../../../../versions";
11
12export const pkgs = `"alchemy@latest" "effect@${effectVersion}" "@effect/platform-bun@${effectVersion}" "@effect/platform-node@${effectVersion}"`;
13
14Install Alchemy and Effect, create a Stack, and deploy a
15`Fly.Service`: an Effect program running in a Fly Machine. The
16Service creates its own Fly App, so it gets its own public
17`https://{name}.fly.dev` URL. Alchemy bundles it, builds a Docker
18image, and pushes it to `registry.fly.io`. No Dockerfile. No
19`fly.toml`.
20
21## Prerequisites
22
23- [Bun](https://bun.sh) or Node.js 22+
24- A Fly organization and an API token — see [Setup](/fly/setup) if
25 you haven't created those yet
26- Docker running locally. Alchemy builds `linux/amd64` images and
27 pushes them with an App deploy token.
28
29:::note[Costs]
30The Service's App and addresses are free. Its Machine (shared-cpu,
31256 MB) is billed by the second. `alchemy destroy` tears everything
32down when you're done.
33:::
34
35## Create a project
36
37Start with an empty directory and initialize a `package.json`:
38
39<Tabs syncKey="pkgManager">
40 <TabItem label="bun" icon="bun">
41 <Code code="mkdir my-app && cd my-app && bun init -y" lang="sh" />
42 </TabItem>
43 <TabItem label="npm" icon="npm">
44 <Code code="mkdir my-app && cd my-app && npm init -y" lang="sh" />
45 </TabItem>
46 <TabItem label="pnpm" icon="pnpm">
47 <Code code="mkdir my-app && cd my-app && pnpm init" lang="sh" />
48 </TabItem>
49 <TabItem label="yarn" icon="seti:yarn">
50 <Code code="mkdir my-app && cd my-app && yarn init -y" lang="sh" />
51 </TabItem>
52</Tabs>
53
54## Install dependencies
55
56Install <code>alchemy@latest</code> and <code>effect@{effectVersion}</code>:
57
58<Tabs syncKey="pkgManager">
59 <TabItem label="bun" icon="bun">
60 <Code code={`bun add ${pkgs}`} lang="sh" />
61 </TabItem>
62 <TabItem label="npm" icon="npm">
63 <Code code={`npm install ${pkgs}`} lang="sh" />
64 </TabItem>
65 <TabItem label="pnpm" icon="pnpm">
66 <Code code={`pnpm add ${pkgs}`} lang="sh" />
67 </TabItem>
68 <TabItem label="yarn" icon="seti:yarn">
69 <Code code={`yarn add ${pkgs}`} lang="sh" />
70 </TabItem>
71</Tabs>
72
73## Create the Stack
74
75Every Alchemy program starts with a `Stack` — a collection of
76Resources managed by Providers with state tracked between deploys.
77
78Create an `alchemy.run.ts` file:
79
80```typescript
81// alchemy.run.ts
82import * as Alchemy from "alchemy";
83import * as Effect from "effect/Effect";
84import * as Layer from "effect/Layer";
85
86export default Alchemy.Stack(
87 "MyApp",
88 {
89 // @error: ts(2345) Argument of type '{ providers: Layer.Layer<never, never, never>; }' is not assignable to parameter of type 'StackProps<never>'.
90 // @error: Property 'state' is missing in type '{ providers: Layer.Layer<never, never, never>; }' but required in type 'StackProps<never>'.
91 providers: Layer.empty,
92 },
93 Effect.gen(function* () {
94 // we'll add resources here next
95 }),
96);
97```
98
99TypeScript is unhappy: the `state` property is required. Every Stack
100needs a **state store** so Alchemy can persist resource state between
101deploys and compute diffs against your infrastructure.
102
103## Configure state
104
105Fly has no state backend of its own, so we'll keep state on disk
106with `Alchemy.localState()` — it writes to `.alchemy/` next to your
107code, no setup required:
108
109```diff lang="typescript"
110// alchemy.run.ts
111import * as Alchemy from "alchemy";
112import * as Effect from "effect/Effect";
113import * as Layer from "effect/Layer";
114
115export default Alchemy.Stack(
116 "MyApp",
117 {
118 providers: Layer.empty,
119+ state: Alchemy.localState(),
120 },
121 Effect.gen(function* () {
122 // we'll add resources here next
123 }),
124);
125```
126
127:::note[Other state stores]
128Local state is perfect for a solo project. When you need state your
129team and CI can share, swap in `Cloudflare.state()` or `AWS.state()`
130— see [State Store](/state-store).
131:::
132
133## Create the Service file
134
135A Service in Alchemy is a class — it has both an infrastructure
136definition and a runtime implementation expressed as an Effect.
137Create `src/api.ts` with the smallest possible declaration:
138
139```typescript
140// src/api.ts
141import * as Fly from "alchemy/Fly";
142import * as Effect from "effect/Effect";
143
144export default class Api extends Fly.Service<Api>()(
145 "Api",
146 { main: import.meta.url },
147 Effect.gen(function* () {
148 return {};
149 }),
150) {}
151```
152
153The `<Api>` type argument plus the empty `()` is a one-time bit of
154ceremony — it lets TypeScript reason about `Api` as a typed handle.
155`main: import.meta.url` tells Alchemy this same file is the bundle
156entrypoint. The Service creates and manages its own Fly App; you
157don't declare one.
158
159## Serve HTTP with `fetch`
160
161Add a `fetch` field — Alchemy treats anything returned from the
162`Effect.gen` block as the runtime API, and `fetch` specifically is
163wired to an HTTP server listening on the Service's port:
164
165```diff lang="typescript"
166// src/api.ts
167import * as Fly from "alchemy/Fly";
168import * as Effect from "effect/Effect";
169+import * as HttpServerResponse from "effect/http/HttpServerResponse";
170
171export default class Api extends Fly.Service<Api>()(
172 "Api",
173 { main: import.meta.url },
174 Effect.gen(function* () {
175- return {};
176+ return {
177+ fetch: Effect.succeed(HttpServerResponse.text("Hello from Fly!")),
178+ };
179 }),
180) {}
181```
182
183`HttpServerResponse` is the same `effect/http` API used on
184every other Alchemy runtime — the handler you write here would run
185unchanged on Cloudflare Workers or AWS Lambda.
186
187:::note
188Notice the "Effect returning an Effect" pattern. The outer generator
189is the **Construction phase** — it runs at deploy time and at process
190startup. The inner `fetch` generator is the **Runtime phase** — it
191runs per request.
192:::
193
194## Add the Service to the Stack
195
196The `Api` class is just a typed identifier — yielding it inside the
197Stack's Effect is what registers the resource. Yield it and observe
198the type error:
199
200```diff lang="typescript"
201// alchemy.run.ts
202import * as Alchemy from "alchemy";
203import * as Effect from "effect/Effect";
204import * as Layer from "effect/Layer";
205+import Api from "./src/api.ts";
206
207export default Alchemy.Stack(
208 "MyApp",
209 {
210 providers: Layer.empty,
211 // @error: ts(2322) Type 'Layer<never, never, never>' is not assignable to type 'Layer<NoInfer<Providers>, never, StackServices>'.
212 // @error: Type 'Providers' is not assignable to type 'never'.
213 state: Alchemy.localState(),
214 },
215 Effect.gen(function* () {
216- // we'll add resources here next
217+ const api = yield* Api;
218 }),
219);
220```
221
222TypeScript is telling us that `Layer.empty` doesn't provide
223`Fly.Providers` — the layer required by `Service`.
224
225## Fix the Providers
226
227Replace `Layer.empty` with `Fly.providers()` to resolve the type
228error:
229
230```diff lang="typescript"
231// alchemy.run.ts
232import * as Alchemy from "alchemy";
233+import * as Fly from "alchemy/Fly";
234import * as Effect from "effect/Effect";
235-import * as Layer from "effect/Layer";
236import Api from "./src/api.ts";
237
238export default Alchemy.Stack(
239 "MyApp",
240 {
241+ providers: Fly.providers(),
242 state: Alchemy.localState(),
243 },
244 Effect.gen(function* () {
245 const api = yield* Api;
246 }),
247);
248```
249
250Now the program type-checks. The providers layer tells Alchemy how to
251talk to the Fly Machines API, and the type system ensures you never
252forget to wire it up.
253
254## Return Stack outputs
255
256Stack outputs let you see important values after a deploy. Return the
257Service's App name and public URL:
258
259```diff lang="typescript"
260Effect.gen(function* () {
261 const api = yield* Api;
262+
263+ return {
264+ appName: api.appName,
265+ url: api.url,
266+ };
267}),
268```
269
270Omit `name` and Alchemy generates a globally unique, DNS-safe App
271name from the stack, stage, and logical ID. A Service is public by
272default: Alchemy allocates a free shared IPv4 and IPv6 on its App,
273and `url` is `https://{appName}.fly.dev`.
274
275## Deploy
276
277Run `alchemy deploy`:
278
279<Tabs syncKey="pkgManager">
280 <TabItem label="bun" icon="bun">
281 <Code code="bun alchemy deploy" lang="sh" />
282 </TabItem>
283 <TabItem label="npm" icon="npm">
284 <Code code="npm run alchemy deploy" lang="sh" />
285 </TabItem>
286 <TabItem label="pnpm" icon="pnpm">
287 <Code code="pnpm alchemy deploy" lang="sh" />
288 </TabItem>
289 <TabItem label="yarn" icon="seti:yarn">
290 <Code code="yarn alchemy deploy" lang="sh" />
291 </TabItem>
292</Tabs>
293
294The first time you deploy, Alchemy prompts for Fly credentials —
295paste the API token you generated in [Setup](/fly/setup). The token
296is verified and saved to your **`default`**
297[profile](/environments/profiles), so you won't be asked again.
298
299<Terminal content={`[u]Plan[/u]: [g]1 to create[/g]
300
301[g]+[/g] [b]Api[/b] [d](Fly.Service)[/d]
302
303Proceed?
304◉ Yes ○ No
305[g]✓[/g] [b]Api[/b] [d](Fly.Service)[/d] [g]created[/g]
306{
307[s2]appName: "myapp-api-dev-a1b2c3d4",
308[s2]url: "https://myapp-api-dev-a1b2c3d4.fly.dev",
309}`} />
310
311Alchemy created the Service's App and its addresses, bundled
312`src/api.ts` with Rolldown, built a `linux/amd64` image, and pushed
313it to `registry.fly.io/{app}:{id}-{hash}`. It then created a Machine
314in `iad`, waited until it was `started`, and pointed the Fly proxy
315at the Service's port.
316
317## Try it out
318
319```sh
320curl https://myapp-api-dev-a1b2c3d4.fly.dev
321# → Hello from Fly!
322```
323
324## Verify it worked
325
326The App is listed in your org in the
327[Fly dashboard](https://fly.io/dashboard). Run `alchemy deploy`
328again. Because nothing changed, the Service shows as a no-op:
329
330<Terminal content={`[u]Plan[/u]: [d]no changes[/d]
331
332{
333[s2]appName: "myapp-api-dev-a1b2c3d4",
334[s2]url: "https://myapp-api-dev-a1b2c3d4.fly.dev",
335}`} />
336
337This is the core loop — declare resources in code, deploy, and
338Alchemy figures out what changed.
339
340## Recap
341
342You now have:
343
344- An `alchemy.run.ts` with a Stack and a `Fly.Service`
345- A live Service in its own App with a generated globally unique name
346- A public `https://{appName}.fly.dev` URL answering HTTP
347
348In [Part 2](/fly/tutorial/part-2), you'll pin the Service's region
349and port, add a health route, and ship a code change.