Environment Variables
How app env vars flow from GitHub secrets into Railway at deploy time.
Apps read environment variables from process.env.X in Railway at runtime. But your app's code, your GitHub repo, and your Railway service are three different places — so the tricky question is "how does a secret I store in GitHub end up in Railway?" This page maps the pipeline.
The Three Places
| Where | What lives there |
|---|---|
| GitHub → Settings → Secrets and variables | The source of truth. secrets for sensitive values, vars for non-sensitive config. |
apps/<app>/deploy.config.yml | A mapping that says "Railway variable X gets its value from GitHub key Y." |
| Railway service environment | Where process.env.X actually reads from at runtime. |
The deploy workflow bridges the three.
The Mapping File
Every app has a deploy.config.yml with a secrets: block. The left side is the variable name your app will see at runtime. The right side is the GitHub key name:
serviceId: "training-tracker"
rootDir: "apps/training-tracker"
secrets:
SLACK_BOT_TOKEN: "TRAINING_TRACKER_SLACK_BOT_TOKEN"
SLACK_WEBHOOK_URL: "TRAINING_TRACKER_SLACK_WEBHOOK_URL"
SLACK_CHANNEL: "TRAINING_TRACKER_SLACK_CHANNEL"
SLACK_SIGNING_SECRET: "TRAINING_TRACKER_SLACK_SIGNING_SECRET"At runtime, process.env.SLACK_BOT_TOKEN in the training-tracker app reads the value stored in GitHub as TRAINING_TRACKER_SLACK_BOT_TOKEN.
The Per-App Workflow Passes the Value
Each app's staging/production workflow forwards GitHub values into the reusable deploy workflow:
secrets:
SLACK_BOT_TOKEN: ${{ secrets.TRAINING_TRACKER_SLACK_BOT_TOKEN }}
SLACK_WEBHOOK_URL: ${{ vars.TRAINING_TRACKER_SLACK_WEBHOOK_URL }}- Use
${{ secrets.X }}for anything sensitive. - Use
${{ vars.X }}for non-sensitive config (e.g. a channel name, a feature flag).
Naming Pattern
To keep GitHub variables collision-free across 30+ apps, prefix with the app name in snake_case:
{APP_NAME_SNAKE}_{VAR_NAME}
Examples: BREAKFAST_TRACKER_SLACK_WEBHOOK_URL, RECRUIT_ASHBY_API_KEY, STAFF_MEAL_TRACKER_OPENAI_API_KEY.
See Naming Conventions.
Adding a New Variable — Checklist
- Add the GitHub key (in the correct environment —
stagingvsproduction) as a secret or var. - Add the mapping to
apps/<your-app>/deploy.config.ymlundersecrets:. - Forward the value in both
deploy-app-<app>-staging.ymlanddeploy-app-<app>-production.yml. - Trigger a deploy and confirm the variable appears in the Railway service environment.
Auto-Injected Variables (No Mapping Needed)
You don't declare these — the platform provides them:
DATABASE_URL— from the environment's database service.AUTH_UI_URL— for every non-auth service.BUCKET_*— storage credentials, if set at the GitHub environment level.
Ask Claude
Troubleshooting
| Symptom | Usually means |
|---|---|
| Variable missing in Railway | GitHub key not set in the right environment, or the workflow forgot to forward it. |
| Value is empty | Referencing vars.X when value lives in secrets.X (or vice versa). |
| Wrong variable name in Railway | Left side of deploy.config.yml is wrong — that's the Railway key. |
If you're not sure how to add a GitHub secret, check with a developer before proceeding.
Quiz
You added `OPENAI_API_KEY: MEAL_PLANNER_OPENAI_API_KEY` to deploy.config.yml and pushed. The deploy ran, but `process.env.OPENAI_API_KEY` is still undefined at runtime. What did you miss?