Infrastructure as Code
All Railway configuration — services, sources, domains, replicas, volumes,
buckets, variable keys — is managed as code with
Railway IaC in the
infra repo. The dashboard is for
reading, not editing: manual edits are drift, and CI flags them daily.
| Directory | Railway project | Environments |
|---|---|---|
huddle-surety/ |
Huddle Surety — App, API, RAG, Notification, their databases and bucket | production, staging, development |
infrastructure/ |
Infrastructure — Temporal (server, UI, Postgres) and the observability stack | production |
Each directory holds one .railway/railway.ts describing the whole project.
One file covers every environment — differences (branch, domains, replica
counts, the production-only legacy o11y stack) are expressed with the
environment context:
const prod = ctx.isEnvironment("production");const branch = prod ? "production" : "staging";const host = (sub: string) => prod ? `${sub}.huddlesurety.co` : `${sub}.${ctx.environmentName}.huddlesurety.co`;Workflow
Section titled “Workflow”Change infrastructure by editing railway.ts and opening a PR:
- PR — CI runs
railway config planfor every project × environment and shows the exact changes in the log. - Merge to
main— CI runsrailway config apply --yes, applying the plan to Railway. - Daily cron — CI re-plans with
--detailed-exit-codeand fails on drift, catching dashboard edits that bypassed the repo.
Destructive changes (deleting a service, variable, or volume) are never
auto-applied: CI deliberately omits --confirm-destructive, so such a plan
fails the workflow and must be applied manually from a terminal.
Locally:
cd infra && bun installcd huddle-surety # or infrastructurerailway environment stagingrailway config plan # needs node_modules/.bin on PATH (railway-iac-ts)What IaC does and doesn’t own
Section titled “What IaC does and doesn’t own”- Owns service settings: source repo/branch, builder, start command, healthcheck, replicas per region, custom domains, TCP proxies, private network endpoints, volumes and mounts, memory limits, canvas groups.
- Owns non-secret wiring variables — environment-derived values
(
DEPLOYMENT_ENVIRONMENT, theapp.…/api.….huddlesurety.coURLs) are literals computed from the environment context, and cross-service URLs are Railway reference templates (http://${{API.RAILWAY_PRIVATE_DOMAIN}}:8080). - Keeps secrets as
preserve()— “keep what’s set in Railway”. IaC guards the key set (removing a key from the file deletes the variable — a destructive change), while secret values stay in Railway. - Doesn’t replace
rag/railpack.json— runtime apt packages (poppler-utils,tesseract-ocr) and the pre-installGITHUB_TOKENgit rewrite for the private SDK have no IaC equivalent. Build plans stay with the builder; service settings live in IaC.rag/railway.tomlwas removed — a service must not be managed by config-as-code and IaC at once.
Gotchas
Section titled “Gotchas”- References into database-class resources (
${{API Postgres.PGHOST}}, ParadeDB’sDATABASE_URL) are redacted by Railway on read-back, so asserting them inrailway.tsre-diffs — and redeploys — on every plan. They are set in Railway but keptpreserve()in the file. - CI needs the
RAILWAY_API_TOKENrepo secret (a Huddle workspace token).