Part 4: Variables and Cleanup
A secret only this Service needs from .env is
Config.redacted. See Variables.
This part is for a value Railway should own: a Variable Railway
injects into services. You will read it with Config, then tear
the stack down so billing stops.
Declare a Variable
Section titled “Declare a Variable”Add a Variable next to the Project. Wrap the value with
Redacted.make so it is never logged:
import * as Railway from "alchemy/Railway";import * as Redacted from "effect/Redacted";
export const Site = Railway.Project("Site");
export const Data = Railway.Volume("Data", { /* ... */ });
export const ApiToken = Railway.Variable("ApiToken", { project: Site, name: "API_TOKEN", value: Redacted.make("not-a-real-token"),});name is the env-var Railway injects into services (API_TOKEN).
Omit it and Alchemy generates an ownership-stamped name instead.
Read it from the Service
Section titled “Read it from the Service”Railway injects the Variable as env. Yield it in init so it is
part of the graph, then read the name with Config — never the
plaintext:
import { Data, Site } from "./project.ts";import { ApiToken, Data, Site } from "./project.ts";import * as Config from "effect/Config";
Effect.gen(function* () { const mount = yield* Railway.MountVolume(Data, { path: "/data" }); const fs = yield* FileSystem.FileSystem; yield* ApiToken; const tokenName = "API_TOKEN"; const token = yield* Config.string(tokenName).pipe( Effect.orElseSucceed(() => ""), );
return { fetch: /* ... */, }; }).pipe(Effect.provide(Railway.MountVolumeLive)),Serve the variable’s name
Section titled “Serve the variable’s name”Add a /secret route that returns the variable’s name, never
the plaintext:
if (url.pathname === "/health") { return HttpServerResponse.json({ ok: true });}if (url.pathname === "/secret") { return HttpServerResponse.json({ name: tokenName, set: token.length > 0, });}const file = `${mount.path}${url.pathname}`;Yield the Variable from the Stack
Section titled “Yield the Variable from the Stack”import { Data, Site } from "./src/project.ts";import { ApiToken, Data, Site } from "./src/project.ts";
Effect.gen(function* () { const site = yield* Site; yield* Data; yield* ApiToken; const api = yield* Api;Deploy
Section titled “Deploy”bun alchemy deploynpm run alchemy deploypnpm alchemy deployyarn alchemy deployPlan: 1 to create, 1 to update + ApiToken (Railway.Variable) ~ Api (Railway.Service) Proceed? ◉ Yes ○ No ✓ ApiToken (Railway.Variable) created ✓ Api (Railway.Service) updated
Try it out
Section titled “Try it out”curl https://myapp-api-dev-a1b2c3d4.up.railway.app/secret# → {"name":"API_TOKEN","set":true}Clean up
Section titled “Clean up”You’re done — tear everything down so Service and Volume billing stops:
bun alchemy destroynpm run alchemy destroypnpm alchemy destroyyarn alchemy destroyAlchemy deletes everything in reverse dependency order — Service, Variable, Volume, Project.
Over four parts you built a complete Railway deployment:
- A Project with a generated unique name and a production environment
- An HTTP Service bundled into a container, updated only when its code hash changes
- A Volume mounted at
/datawhose contents outlive deploys - A Variable readable from the Service, never logged
Where next
Section titled “Where next”- Services — background workers, env, multiple Services per Project, image-based Services.
- Volumes — attach at create time, region rules.
- Variables —
Config.redactedvsRailway.Variable. - Postgres — bind
ConnectPostgresand query with Drizzle. - Testing — deploy this stack from an integration test and drive it over HTTP.
- CI — run
alchemy deployfrom GitHub Actions withRAILWAY_API_TOKEN.