1---
2title: "Part 2: Configure the Service"
3description: Pin the Service's region and port, add a health route, and ship a code change.
4sidebar:
5 order: 2
6---
7
8import Terminal from "../../../../components/Terminal.astro";
9import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
10
11In [Part 1](/fly/tutorial/part-1) you deployed a Service on its own
12`fly.dev` hostname. Now you will pin where it runs and which port it
13listens on, add a health route, and ship a change.
14
15## Pin a region
16
17Fly Machines live in a [region](/fly/compute/regions). The default
18is `iad` (Ashburn). Set it explicitly so the Volume you add in
19Part 3 can match:
20
21```diff lang="typescript"
22export default class Api extends Fly.Service<Api>()(
23 "Api",
24- { main: import.meta.url },
25+ { main: import.meta.url, region: "iad" },
26```
27
28:::caution[Changing `region` moves the Machine]
29A new Machine is created in the new region and the old one is
30deleted; the hostname stays the same. [Regions](/fly/compute/regions)
31lists the codes.
32:::
33
34## Set the port
35
36Give the Service an explicit port so the Fly proxy knows where to
37forward:
38
39```diff lang="typescript"
40export default class Api extends Fly.Service<Api>()(
41 "Api",
42- { main: import.meta.url, region: "iad" },
43+ { main: import.meta.url, region: "iad", port: 3000 },
44```
45
46The port is written to the process environment as `PORT` and used as
47the Machine's `internal_port`. By default Alchemy publishes HTTP 80
48and HTTPS 443 on the Fly proxy in front of it.
49
50## Add a health route
51
52Add `/health` so you (and later, Fly checks) can probe the process:
53
54```diff lang="typescript"
55// src/api.ts
56+import { HttpServerRequest } from "effect/http/HttpServerRequest";
57// ...
58 return {
59- fetch: Effect.succeed(HttpServerResponse.text("Hello from Fly!")),
60+ fetch: Effect.gen(function* () {
61+ const request = yield* HttpServerRequest;
62+ const url = new URL(request.url, "http://service");
63+ if (url.pathname === "/health") {
64+ return HttpServerResponse.json({ ok: true });
65+ }
66+ return HttpServerResponse.text("Hello from Fly!");
67+ }),
68 };
69```
70
71## Deploy
72
73<Tabs syncKey="pkgManager">
74 <TabItem label="bun" icon="bun">
75 <Code code="bun alchemy deploy" lang="sh" />
76 </TabItem>
77 <TabItem label="npm" icon="npm">
78 <Code code="npm run alchemy deploy" lang="sh" />
79 </TabItem>
80 <TabItem label="pnpm" icon="pnpm">
81 <Code code="pnpm alchemy deploy" lang="sh" />
82 </TabItem>
83 <TabItem label="yarn" icon="seti:yarn">
84 <Code code="yarn alchemy deploy" lang="sh" />
85 </TabItem>
86</Tabs>
87
88<Terminal content={`[u]Plan[/u]: [y]1 to update[/y]
89
90[y]~[/y] [b]Api[/b] [d](Fly.Service)[/d]
91
92Proceed?
93◉ Yes ○ No
94[g]✓[/g] [b]Api[/b] [d](Fly.Service)[/d] [y]updated[/y]
95{
96[s2]appName: "myapp-api-dev-a1b2c3d4",
97[s2]url: "https://myapp-api-dev-a1b2c3d4.fly.dev",
98}`} />
99
100`iad` and port `3000` match the defaults from Part 1, so the Machine
101stays where it is. The new route changes the bundle hash, so Alchemy
102builds a new image and updates the Machine in place. The App and its
103URL stay the same.
104
105## Try it out
106
107```sh
108curl https://myapp-api-dev-a1b2c3d4.fly.dev/health
109# → {"ok":true}
110```
111
112## Ship a change
113
114Edit the greeting in `src/api.ts` and deploy again:
115
116<Terminal content={`[u]Plan[/u]: [y]1 to update[/y]
117
118[y]~[/y] [b]Api[/b] [d](Fly.Service)[/d]
119
120Proceed?
121◉ Yes ○ No
122[g]✓[/g] [b]Api[/b] [d](Fly.Service)[/d] [y]updated[/y]`} />
123
124Alchemy hashes the bundle: if your code didn't change, the Service
125is a no-op; if it did, a new image is built, pushed, and the Machine
126updated in place.
127
128## Recap
129
130You now have:
131
132- An HTTP Service running as a Fly Machine in `iad` on port 3000
133- A `/health` route on its public `https://{appName}.fly.dev` URL
134- A code-hash-based update loop — edit, deploy, new image
135
136In [Part 3](/fly/tutorial/part-3), you'll attach a Volume and
137persist data across deploys.