1---
2title: "Part 4: Secrets and Cleanup"
3description: Read a secret from .env in the Service with Config.Redacted, guard a route with it, and destroy the stack.
4sidebar:
5 order: 4
6---
7
8import Terminal from "../../../../components/Terminal.astro";
9import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
10
11In this part the Service reads an API token from your `.env` with
12`Config.Redacted` and uses it to guard a route. Then you tear the
13stack down so billing stops.
14
15## Add the secret to `.env`
16
17Create a `.env` file next to `alchemy.run.ts`:
18
19```sh
20# .env
21API_TOKEN=not-a-real-token
22```
23
24Alchemy reads Config values from the env of whoever runs the deploy,
25which includes `.env`. Keep `.env` out of version control.
26
27## Read it in the constructor
28
29Yield `Config.Redacted` in the Service's constructor Effect:
30
31```diff lang="typescript"
32// src/api.ts
33+import * as Config from "effect/Config";
34// ...
35 Effect.gen(function* () {
36 const mount = yield* Fly.MountVolume({ path: "/data", sizeGb: 1 });
37 const fs = yield* FileSystem.FileSystem;
38+ const apiToken = yield* Config.Redacted("API_TOKEN");
39
40 return {
41 fetch: /* ... */,
42 };
43 }).pipe(Effect.provide(Fly.MountVolumeLive)),
44```
45
46At deploy time Alchemy reads `API_TOKEN` and writes it onto the
47Machine as a secret. At runtime the same line resolves from that env
48var. `apiToken` is `Redacted<string>`, so it is never logged.
49
50## Guard a route with the token
51
52Add a `/secret` route that answers only when the request carries the
53token:
54
55```diff lang="typescript"
56// src/api.ts
57+import * as Redacted from "effect/Redacted";
58// ...
59if (url.pathname === "/health") {
60 return HttpServerResponse.json({ ok: true });
61}
62+if (url.pathname === "/secret") {
63+ const expected = `Bearer ${Redacted.value(apiToken)}`;
64+ return request.headers["authorization"] === expected
65+ ? HttpServerResponse.text("authorized")
66+ : HttpServerResponse.text("unauthorized", { status: 401 });
67+}
68const file = `${mount.path}${url.pathname}`;
69```
70
71`Redacted.value` unwraps the token only where the comparison needs
72the raw string.
73
74## Deploy
75
76<Tabs syncKey="pkgManager">
77 <TabItem label="bun" icon="bun">
78 <Code code="bun alchemy deploy" lang="sh" />
79 </TabItem>
80 <TabItem label="npm" icon="npm">
81 <Code code="npm run alchemy deploy" lang="sh" />
82 </TabItem>
83 <TabItem label="pnpm" icon="pnpm">
84 <Code code="pnpm alchemy deploy" lang="sh" />
85 </TabItem>
86 <TabItem label="yarn" icon="seti:yarn">
87 <Code code="yarn alchemy deploy" lang="sh" />
88 </TabItem>
89</Tabs>
90
91<Terminal content={`[u]Plan[/u]: [y]1 to update[/y]
92
93[y]~[/y] [b]Api[/b] [d](Fly.Service)[/d]
94
95Proceed?
96◉ Yes ○ No
97[g]✓[/g] [b]Api[/b] [d](Fly.Service)[/d] [y]updated[/y]`} />
98
99## Try it out
100
101```sh
102curl https://myapp-api-dev-a1b2c3d4.fly.dev/secret
103# → unauthorized
104
105curl -H "Authorization: Bearer not-a-real-token" \
106 https://myapp-api-dev-a1b2c3d4.fly.dev/secret
107# → authorized
108```
109
110:::tip[Sharing a secret or adding your own domain?]
111A value several Services share belongs in a
112[`Fly.Secret`](/fly/data/secrets#when-fly-should-own-the-secret), and
113a custom domain needs a
114[`Fly.Certificate`](/fly/networking#use-your-own-hostname). Both
115attach to a `Fly.App`, so the Services that use them join that App
116with [`app`](/fly/compute/services#group-services-in-one-app).
117:::
118
119## Clean up
120
121You're done — tear everything down so Machine and Volume billing
122stops:
123
124<Tabs syncKey="pkgManager">
125 <TabItem label="bun" icon="bun">
126 <Code code="bun alchemy destroy" lang="sh" />
127 </TabItem>
128 <TabItem label="npm" icon="npm">
129 <Code code="npm run alchemy destroy" lang="sh" />
130 </TabItem>
131 <TabItem label="pnpm" icon="pnpm">
132 <Code code="pnpm alchemy destroy" lang="sh" />
133 </TabItem>
134 <TabItem label="yarn" icon="seti:yarn">
135 <Code code="yarn alchemy destroy" lang="sh" />
136 </TabItem>
137</Tabs>
138
139Deleting the Service deletes its Machine, its Volume, and the App it
140created, along with the App's addresses.
141
142## Recap
143
144Over four parts you built a complete Fly deployment:
145
146- An HTTP Service in its own App with a generated globally unique
147 name and a public `fly.dev` URL
148- Machines updated only when the code hash changes
149- A Volume mounted at `/data` whose contents outlive deploys
150- A secret from `.env` readable from the Service, never logged
151
152## Where next
153
154- [Services](/fly/compute/services) — private Services, background
155 workers, and grouping Services in one App.
156- [IPs & certificates](/fly/networking) — public and private
157 Services, and your own hostname.
158- [Machines](/fly/compute/machines) — raw images without a bundle.
159- [Volumes](/fly/data/volumes) — snapshots, extend, region rules.
160- [Secrets](/fly/data/secrets) — `Fly.Secret`, `GetSecret` /
161 `ListSecrets` / `WriteSecret`, plus KMS `SecretKey` with
162 `Encrypt` / `Decrypt` / `Sign` / `Verify`.
163- [Testing](/testing) — deploy this stack from an integration test
164 and drive it over HTTP.
165- [CI](/environments/ci) — run `alchemy deploy` from GitHub Actions
166 with `FLY_API_TOKEN`.