Deployment

How apps ship to staging and production on Railway via GitHub Actions.

Deployment is fully automated. You commit, open a PR, and your app lands on staging with a preview URL. Merge to main, and it lands on production. Here's what's actually happening under the hood so you can debug when something goes sideways.

Architecture

  • 1 Railway Project — the platform boundary (auth, networking).
  • N Railway Services — one per app in apps/*. Each service deploys independently.
  • Railpack builds the app (zero-config); it reads the root pnpm-workspace.yaml and builds only your app plus its dependencies.

Per-App Wiring

Each app needs three things:

  1. apps/<app-name>/deploy.config.yml — points at the Railway service and declares env-var mappings.
  2. .github/workflows/deploy-app-<app-name>-staging.yml — a manual workflow_dispatch workflow (used for one-off staging deploys; not what runs on a PR — see next section).
  3. .github/workflows/deploy-app-<app-name>-production.yml — triggers on push to main with a path filter. Most apps filter to apps/<app-name>/** + shared/database/**; a few apps that depend more broadly on shared code (e.g. vibe-coding-guide, staff-portal) filter shared/** instead. Check your app's workflow file to see which.

Both app workflows call the shared .github/workflows/deploy-reusable.yml, which does the actual work: pnpm install --frozen-lockfile → optional test → pnpm --filter <app> buildrailway up.

The Two Paths a Deploy Can Take

Staging (when you open a PR)

A single workflow, pr-preview-neon.yml, orchestrates the whole preview:

  1. You push to your feature branch and open a PR to main.
  2. If the schema changed, a migration is generated and committed back to the PR.
  3. One Neon DB branch is created for the PR (preview/pr-<number>-<branch>), 14-day expiry, with migrations applied.
  4. pr-preview-neon.yml discovers which apps changed and, in a matrix job, calls deploy-reusable.yml directly for each one — it does not dispatch the per-app staging workflows.
  5. Each changed app deploys to Railway's staging environment, connected to the shared Neon PR branch.
  6. A PR comment is posted with a preview URL per affected app.

The Neon PR branch is deleted when the PR closes.

Production (when you merge)

  1. You merge the PR to main.
  2. The production workflow for each affected app triggers via the path filter.
  3. Migrations apply to Neon main.
  4. Railway redeploys the production service.

Why You Don't See Secrets in Code

Railway environment variables are injected by the reusable workflow at deploy time. The workflow reads apps/<your-app>/deploy.config.yml's secrets: block, pulls values from GitHub secrets/vars, and pushes them to the Railway service environment. See Environment Variables for the full mapping flow.

Logs

Railway captures stdout and stderr only. Use console.log, console.warn, console.error. In production, log JSON so you can search on fields:

Structured loggingtypescript
console.error(JSON.stringify({
level: 'error',
event: 'api_error',
error: error instanceof Error ? error.message : 'Unknown',
timestamp: new Date().toISOString(),
}));

Never log PII (emails, names) or secrets. Never write logs to files or the database.

Auto-injected Variables

You don't need to declare these — they're set by the platform:

  • DATABASE_URL — injected from the environment's database service.
  • AUTH_UI_URL — injected for every non-auth service.

Ask Claude

Add a new app to the deploy pipeline
Claude prompt
I just scaffolded a new app called meal-planner from the template. Set up the Railway deploy.config.yml and the two GitHub Actions workflow files (deploy-app-meal-planner-staging.yml and deploy-app-meal-planner-production.yml). Use apps/checkin-board/ as a reference.

When a Deploy Fails

  • Check the workflow run in GitHub Actions to see which step failed.
  • Lint/typecheck fail → run pnpm build:<your-app> locally to reproduce.
  • railway up fails → open the Railway dashboard; service logs will usually tell you why.
  • Missing env var → see Environment Variables.
  • Migration fail → see Database Migration Errors.

Quiz

Quiz

You merge a PR to main that only changes files under apps/meal-planner/. Which production services redeploy?