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

WhereWhat lives there
GitHub → Settings → Secrets and variablesThe source of truth. secrets for sensitive values, vars for non-sensitive config.
apps/<app>/deploy.config.ymlA mapping that says "Railway variable X gets its value from GitHub key Y."
Railway service environmentWhere 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:

apps/training-tracker/deploy.config.ymlyaml
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:

.github/workflows/deploy-app-training-tracker-production.yml (excerpt)yaml
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

  1. Add the GitHub key (in the correct environment — staging vs production) as a secret or var.
  2. Add the mapping to apps/<your-app>/deploy.config.yml under secrets:.
  3. Forward the value in both deploy-app-<app>-staging.yml and deploy-app-<app>-production.yml.
  4. 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

Add a new secret to an app
Claude prompt
My meal-planner app needs a new env var OPENAI_API_KEY at runtime. Walk me through: (1) what GitHub key name to use, (2) the deploy.config.yml mapping, (3) what to add to deploy-app-meal-planner-staging.yml and deploy-app-meal-planner-production.yml. Follow the monorepo naming convention.

Troubleshooting

SymptomUsually means
Variable missing in RailwayGitHub key not set in the right environment, or the workflow forgot to forward it.
Value is emptyReferencing vars.X when value lives in secrets.X (or vice versa).
Wrong variable name in RailwayLeft 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

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?