Part 3: Persist Data with a Volume
Your Service from Part 2 is stateless. Writes to the Machine’s root disk die with the Machine. In this part you mount a Volume so files survive deploys.
Mount a disk into the Service
Section titled “Mount a disk into the Service”There is no standalone Volume resource. Inside the Service’s init,
Fly.MountVolume({ path, sizeGb }) creates a per-replica disk in
the Service’s app and region:
import * as Fly from "alchemy/Fly";import * as Effect from "effect/Effect";import { HttpServerRequest } from "effect/unstable/http/HttpServerRequest";import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";import { Site } from "./app.ts";
export default class Api extends Fly.Service<Api>()( "Api", { app: Site, main: import.meta.url, region: "iad", port: 3000 }, Effect.gen(function* () { const mount = yield* Fly.MountVolume({ path: "/data", sizeGb: 1 });
return { fetch: /* ... */, }; }),) {}MountVolume is a binding: at deploy time it tells the Service
“create a 1 GB Volume and put it in config.mounts at /data”, and
at runtime it hands you the resolved mount.path.
Provide the binding layer
Section titled “Provide the binding layer”Bindings declare a capability; layers implement it. Provide
MountVolumeLive on the Service’s init Effect:
Effect.gen(function* () { const mount = yield* Fly.MountVolume({ path: "/data", sizeGb: 1 });
return { fetch: /* ... */, }; }), }).pipe(Effect.provide(Fly.MountVolumeLive)),) {}Resolve FileSystem in init
Section titled “Resolve FileSystem in init”Yield FileSystem in the outer Effect. Close over it in fetch.
Do not yield it per request.
import * as FileSystem from "effect/FileSystem";// ... Effect.gen(function* () { const mount = yield* Fly.MountVolume({ path: "/data", sizeGb: 1 }); const fs = yield* FileSystem.FileSystem;
return { fetch: /* ... */, }; }).pipe(Effect.provide(Fly.MountVolumeLive)),Write files with PUT /:name
Section titled “Write files with PUT /:name”The Volume is a directory. Use fs from init:
fetch: Effect.gen(function* () { const request = yield* HttpServerRequest; const url = new URL(request.url, "http://service"); if (url.pathname === "/health") { return HttpServerResponse.json({ ok: true }); } const file = `${mount.path}${url.pathname}`;
if (request.method === "PUT") { const body = yield* request.text; yield* fs.writeFileString(file, body).pipe(Effect.orDie); return HttpServerResponse.empty({ status: 204 }); }
return HttpServerResponse.text("Hello from Fly!");}),Read files with GET /:name
Section titled “Read files with GET /:name”Add the read branch, turning a missing file into a 404:
if (request.method === "PUT") { const body = yield* request.text; yield* fs.writeFileString(file, body).pipe(Effect.orDie); return HttpServerResponse.empty({ status: 204 });}
if (request.method === "GET") { const text = yield* fs.readFileString(file).pipe( Effect.catchAll(() => Effect.succeed(undefined)), ); if (text === undefined) { return HttpServerResponse.text("Not found", { status: 404 }); } return HttpServerResponse.text(text);}
return HttpServerResponse.text("Hello from Fly!");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
During the Service’s deploy, Alchemy creates the Volume and writes
it into the Machine’s config.mounts at /data. The disk lives in
the Service’s region (iad).
Try it out
Section titled “Try it out”# Store a file on the Volumecurl -X PUT https://myapp-site-dev-a1b2c3d4.fly.dev/hello.txt -d 'Hello, Volume!'
# Read it backcurl https://myapp-site-dev-a1b2c3d4.fly.dev/hello.txt# → Hello, Volume!Ship another code change and deploy — the file is still there. If the Machine is replaced, the Volume re-attaches on the next deploy.
You now have:
- A 1 GB Volume in
iadmounted at/dataon the API Machine - A Service reading and writing it through Effect’s
FileSystem— no SDK, just files - Data that outlives deploys and Machine replacement
In Part 4, you’ll store a secret on the App and read it from the Service, then tear the stack down.