1---
2title: "Part 4: Variables and Cleanup"
3description: Store a Project variable, read it from the Service at runtime, 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
11A secret only this Service needs from `.env` is
12`Config.Redacted`. See [Variables](/railway/data/variables).
13
14This part is for a value Railway should own: a **Variable** Railway
15injects into services. You will read it with `Config`, then tear
16the stack down so billing stops.
17
18## Declare a Variable
19
20Add a Variable next to the Project. Wrap the value with
21`Redacted.make` so it is never logged:
22
23```diff lang="typescript"
24// src/project.ts
25import * as Railway from "alchemy/Railway";
26+import * as Redacted from "effect/Redacted";
27
28export const Site = Railway.Project("Site");
29
30export const Data = Railway.Volume("Data", { /* ... */ });
31+
32+export const ApiToken = Railway.Variable("ApiToken", {
33+ project: Site,
34+ name: "API_TOKEN",
35+ value: Redacted.make("not-a-real-token"),
36+});
37```
38
39`name` is the env-var Railway injects into services (`API_TOKEN`).
40Omit it and Alchemy generates an ownership-stamped name instead.
41
42## Read it from the Service
43
44Railway injects the Variable as env. Yield it in the constructor so it is
45part of the graph, then read the name with `Config` — never the
46plaintext:
47
48```diff lang="typescript"
49// src/api.ts
50-import { Data, Site } from "./project.ts";
51+import { ApiToken, Data, Site } from "./project.ts";
52+import * as Config from "effect/Config";
53
54 Effect.gen(function* () {
55 const mount = yield* Railway.MountVolume(Data, { path: "/data" });
56 const fs = yield* FileSystem.FileSystem;
57+ yield* ApiToken;
58+ const tokenName = "API_TOKEN";
59+ const token = yield* Config.String(tokenName).pipe(
60+ Effect.orElseSucceed(() => ""),
61+ );
62
63 return {
64 fetch: /* ... */,
65 };
66 }).pipe(Effect.provide(Railway.MountVolumeLive)),
67```
68
69## Serve the variable's name
70
71Add a `/secret` route that returns the variable's **name**, never
72the plaintext:
73
74```typescript
75if (url.pathname === "/health") {
76 return HttpServerResponse.json({ ok: true });
77}
78if (url.pathname === "/secret") {
79 return HttpServerResponse.json({
80 name: tokenName,
81 set: token.length > 0,
82 });
83}
84const file = `${mount.path}${url.pathname}`;
85```
86
87## Yield the Variable from the Stack
88
89```diff lang="typescript"
90// alchemy.run.ts
91-import { Data, Site } from "./src/project.ts";
92+import { ApiToken, Data, Site } from "./src/project.ts";
93
94 Effect.gen(function* () {
95 const site = yield* Site;
96 yield* Data;
97+ yield* ApiToken;
98 const api = yield* Api;
99```
100
101## Deploy
102
103<Tabs syncKey="pkgManager">
104 <TabItem label="bun" icon="bun">
105 <Code code="bun alchemy deploy" lang="sh" />
106 </TabItem>
107 <TabItem label="npm" icon="npm">
108 <Code code="npm run alchemy deploy" lang="sh" />
109 </TabItem>
110 <TabItem label="pnpm" icon="pnpm">
111 <Code code="pnpm alchemy deploy" lang="sh" />
112 </TabItem>
113 <TabItem label="yarn" icon="seti:yarn">
114 <Code code="yarn alchemy deploy" lang="sh" />
115 </TabItem>
116</Tabs>
117
118<Terminal content={`[u]Plan[/u]: [g]1 to create[/g], [y]1 to update[/y]
119
120[g]+[/g] [b]ApiToken[/b] [d](Railway.Variable)[/d]
121[y]~[/y] [b]Api[/b] [d](Railway.Service)[/d]
122
123Proceed?
124◉ Yes ○ No
125[g]✓[/g] [b]ApiToken[/b] [d](Railway.Variable)[/d] [g]created[/g]
126[g]✓[/g] [b]Api[/b] [d](Railway.Service)[/d] [y]updated[/y]`} />
127
128## Try it out
129
130```sh
131curl https://myapp-api-dev-a1b2c3d4.up.railway.app/secret
132# → {"name":"API_TOKEN","set":true}
133```
134
135:::tip[Your own domain?]
136Declare a [`Railway.CustomDomain`](/railway/networking#custom-domains)
137for a hostname you control and point DNS at
138`verificationDnsHost` / `verificationToken`. Postgres and Redis on
139the private network stay on `{name}.railway.internal`; a
140[`TcpProxy`](/railway/networking#tcp-proxies) is what a laptop uses.
141:::
142
143## Clean up
144
145You're done — tear everything down so Service and Volume billing
146stops:
147
148<Tabs syncKey="pkgManager">
149 <TabItem label="bun" icon="bun">
150 <Code code="bun alchemy destroy" lang="sh" />
151 </TabItem>
152 <TabItem label="npm" icon="npm">
153 <Code code="npm run alchemy destroy" lang="sh" />
154 </TabItem>
155 <TabItem label="pnpm" icon="pnpm">
156 <Code code="pnpm alchemy destroy" lang="sh" />
157 </TabItem>
158 <TabItem label="yarn" icon="seti:yarn">
159 <Code code="yarn alchemy destroy" lang="sh" />
160 </TabItem>
161</Tabs>
162
163Alchemy deletes everything in reverse dependency order — Service,
164Variable, Volume, Project.
165
166## Recap
167
168Over four parts you built a complete Railway deployment:
169
170- A Project with a generated unique name and a production environment
171- An HTTP Service bundled into a container, updated only when its
172 code hash changes
173- A Volume mounted at `/data` whose contents outlive deploys
174- A Variable readable from the Service, never logged
175
176## Where next
177
178- [Services](/railway/compute/services) — background workers, env,
179 multiple Services per Project, image-based Services.
180- [Volumes](/railway/data/volumes) — attach at create time, region
181 rules.
182- [Variables](/railway/data/variables) — `Config.Redacted` vs
183 `Railway.Variable`.
184- [Postgres](/railway/data/postgres) — bind `ConnectPostgres` and
185 query with Drizzle.
186- [Testing](/testing) — deploy this stack from an integration test
187 and drive it over HTTP.
188- [CI](/environments/ci) — run `alchemy deploy` from GitHub Actions
189 with `RAILWAY_API_TOKEN`.