Railway
Your whole production (Postgres, Redis, api, web, site) is described in .railway/railway.ts.
You preview what it would change and apply it, instead of clicking it together in the
dashboard.
What you need
Section titled “What you need”- A Railway account, and Railway’s GitHub app installed on the account or organisation that owns your repository, with access to it: github.com/apps/railway-app/installations/new. Without it the services are created but never build. Railway builds from GitHub, so push your code first.
- An email provider. Without one nobody can verify their address, so nobody can sign in. See Email provider.
- Node 22 or newer. Railway’s CLI runs
.railway/railway.tswith it.
Deploy for the first time
Section titled “Deploy for the first time”-
Create your production settings file:
Terminal window bun run deploy:initIt writes
.env.railwaywith a newAUTH_SECRET. The file is never committed. -
Fill in
.env.railway. The minimum:Terminal window RAILWAY_PROJECT=my-saasADMIN_EMAILS=you@example.comSMTP_HOST=smtp.mailgun.orgSMTP_PORT=587SMTP_USER=...SMTP_PASSWORD=...MAIL_FROM=My SaaS <hello@example.com>Billing, AI and the rest can wait.
RAILWAY_REPOcan stay empty: it defaults to the repository this folder was cloned from. -
Sign in and create the project, with the name you wrote in
RAILWAY_PROJECT:Terminal window bun run railway loginbun run railway init -
Preview, then apply.
deploy:planchanges nothing;deploy:applychecks.env.railwayagainst the API’s rules (Before you deploy), then shows the plan again and asks first:Terminal window bun run deploy:planbun run deploy:apply -
Give the app and the site an address, before anything deploys. The API builds its links from the web app’s address, and refuses to start without one:
Terminal window bun run railway domain --service web --port 8080bun run railway domain --service site --port 8080 -
Connect each service to your repository. Applying records which repository to build, but does not build it or deploy on push; connecting does both, starting with your latest commit:
Terminal window bun run railway service source connect --repo <owner/name> --branch main --service apibun run railway service source connect --repo <owner/name> --branch main --service webbun run railway service source connect --repo <owner/name> --branch main --service siteFollow a service with
bun run railway logs --service api. The API’s first lines areMigrations applied.. -
Check it the way a visitor reaches it:
Terminal window bun run deploy:check https://<web address> --site https://<site address> --email you@example.com
bun run railway is Railway’s CLI pinned to the version the kit is tested with. Use it
instead of a railway you have installed.
What .env.railway controls
Section titled “What .env.railway controls”It is the only file .railway/railway.ts reads; your shell’s environment is ignored.
| Variable | Effect |
|---|---|
RAILWAY_PROJECT |
The project it may change. See the guard below |
RAILWAY_REPO, RAILWAY_BRANCH |
What Railway builds. Default: this repository, main |
WEB_DOMAIN, SITE_DOMAIN |
Your own domains. Empty uses Railway’s addresses |
RAILWAY_WORKER |
true runs jobs in a separate worker service |
| Everything else | Passed to the API. See Environment variables |
A value you leave empty is left as it is on Railway, so a secret can live only in
Railway’s dashboard. DATABASE_URL, WEB_URL, PORT, API_URL and the site’s addresses are
worked out for you.
The guard against the wrong project
Section titled “The guard against the wrong project”The CLI can take its project link from a folder above this one, which may be another app’s
production. So the file stops unless the linked project is named what RAILWAY_PROJECT
says:
This folder is linked to the Railway project "other-app", and .env.railway saysRAILWAY_PROJECT=my-saas. Nothing was changed.Fix it with bun run railway link. Read every plan before you apply it anyway.
What happens on each release
Section titled “What happens on each release”A push to your branch rebuilds only the services whose files changed: a change in apps/web
leaves the API running. Then, for each one:
- For the API, migrations run first. If they fail, the release stops and the version that is running keeps serving.
- The new version starts, and Railway asks it whether it is healthy:
/api/healthfor the API,/healthzfor the web app and the site. See Health checks. - Only once it answers does it take over. If it never does, the old version stays.
A service that exits is restarted, up to 5 times in a row. So a migration must work with the code that is still running: add a column in one release, stop using the old one in the next.
Use your own domains
Section titled “Use your own domains”Do it in this order. Sign-in only accepts requests from the app’s own address, so if the app moved to a domain whose DNS is not ready, nobody could sign in until it was.
-
Attach the domain. It prints the DNS records to add:
Terminal window bun run railway domain app.example.com --service web --port 8080 -
Add those records (a CNAME and a TXT) where your domain’s DNS is managed, and wait until
https://app.example.comloads. -
Set
WEB_DOMAIN=app.example.comin.env.railway(nohttps://), thendeploy:plananddeploy:apply. The API’sWEB_URLand the site’s links follow it. From then on the Railway address still shows the app, but sign-in refuses it. -
Give Stripe or Polar (the webhook endpoint), Google and GitHub the new address, if you use them.
The same goes for the site with SITE_DOMAIN and --service site.
Run jobs in their own service
Section titled “Run jobs in their own service”Set RAILWAY_WORKER=true and apply. A worker service starts from the API image with
bun src/worker.ts, and the API gets JOBS_WORKER=false.
Change production later
Section titled “Change production later”- Release code: push to the deployed branch.
- Change a variable, domain or service: edit
.env.railwayor.railway/railway.ts, then plan and apply. A new API variable goes inapiSettingsinpackages/deploy/src/production.ts, inYOURSin.railway/railway.ts, and in.env.railway.exampleand.env.aws.example. A test fails until they agree.
Don’t change settings in Railway’s dashboard: according to Railway’s docs, applying the file removes what it doesn’t describe. The plan marks removals.
Errors: see Troubleshooting.