Part 4: Secrets and Cleanup
In this part the Service reads an API token from your .env with
Config.Redacted and uses it to guard a route. Then you tear the
stack down so billing stops.
Add the secret to .env
Section titled “Add the secret to .env”Create a .env file next to alchemy.run.ts:
API_TOKEN=not-a-real-tokenAlchemy reads Config values from the env of whoever runs the deploy,
which includes .env. Keep .env out of version control.
Read it in the constructor
Section titled “Read it in the constructor”Yield Config.Redacted in the Service’s constructor Effect:
import * as Config from "effect/Config";// ... Effect.gen(function* () { const mount = yield* Fly.MountVolume({ path: "/data", sizeGb: 1 }); const fs = yield* FileSystem.FileSystem; const apiToken = yield* Config.Redacted("API_TOKEN");
return { fetch: /* ... */, }; }).pipe(Effect.provide(Fly.MountVolumeLive)),At deploy time Alchemy reads API_TOKEN and writes it onto the
Machine as a secret. At runtime the same line resolves from that env
var. apiToken is Redacted<string>, so it is never logged.
Guard a route with the token
Section titled “Guard a route with the token”Add a /secret route that answers only when the request carries the
token:
import * as Redacted from "effect/Redacted";// ...if (url.pathname === "/health") { return HttpServerResponse.json({ ok: true });}if (url.pathname === "/secret") { const expected = `Bearer ${Redacted.value(apiToken)}`; return request.headers["authorization"] === expected ? HttpServerResponse.text("authorized") : HttpServerResponse.text("unauthorized", { status: 401 });}const file = `${mount.path}${url.pathname}`;Redacted.value unwraps the token only where the comparison needs
the raw string.
Deploy
Section titled “Deploy”bun alchemy deploynpm run alchemy deploypnpm alchemy deployyarn alchemy deployPlan: 1 to update ~ Api (Fly.Service) Proceed? ◉ Yes ○ No ✓ Api (Fly.Service) updated
Try it out
Section titled “Try it out”curl https://myapp-api-dev-a1b2c3d4.fly.dev/secret# → unauthorized
curl -H "Authorization: Bearer not-a-real-token" \ https://myapp-api-dev-a1b2c3d4.fly.dev/secret# → authorizedClean up
Section titled “Clean up”You’re done — tear everything down so Machine and Volume billing stops:
bun alchemy destroynpm run alchemy destroypnpm alchemy destroyyarn alchemy destroyDeleting the Service deletes its Machine, its Volume, and the App it created, along with the App’s addresses.
Over four parts you built a complete Fly deployment:
- An HTTP Service in its own App with a generated globally unique
name and a public
fly.devURL - Machines updated only when the code hash changes
- A Volume mounted at
/datawhose contents outlive deploys - A secret from
.envreadable from the Service, never logged
Where next
Section titled “Where next”- Services — private Services, background workers, and grouping Services in one App.
- IPs & certificates — public and private Services, and your own hostname.
- Machines — raw images without a bundle.
- Volumes — snapshots, extend, region rules.
- Secrets —
Fly.Secret,GetSecret/ListSecrets/WriteSecret, plus KMSSecretKeywithEncrypt/Decrypt/Sign/Verify. - Testing — deploy this stack from an integration test and drive it over HTTP.
- CI — run
alchemy deployfrom GitHub Actions withFLY_API_TOKEN.