This repository uses Railway as the default public hosting target for the production demo environment.
- container-based deployment using the existing
Dockerfile - a GitHub Actions deployment workflow with explicit verification, deployment, and post-deploy validation phases
- a pre-deploy migration step through
npm run prisma:migrate:deploy - post-deploy smoke validation for
/health,/ready, and/docs.json
The repository uses two different automation paths on purpose:
CIin.github/workflows/ci.ymlprotectsmainand remains the branch-quality gateDeployin.github/workflows/deploy.ymlis the promotion workflow and always re-verifies the exact ref that will be deployed
Production promotion is intentionally stricter than non-production:
- production deploys happen only from published GitHub releases
- manual dispatch is reserved for staging or homolog-style environments
- manual dispatch rejects
productionto avoid ad-hoc promotion bypasses - production smoke validation is mandatory and must have a public HTTPS base URL configured
This design keeps branch quality and environment promotion separate, which produces cleaner deployment history and a more auditable release trail.
Create a Railway project with three services:
auth-apifor the application- PostgreSQL
- Redis
Generate a public domain for the application service and keep the final HTTPS base URL available for GitHub Actions smoke checks.
Use .env.production.example as the source of truth for the application variables.
At minimum, configure:
DATABASE_URLfrom the Railway PostgreSQL serviceREDIS_URLfrom the Railway Redis serviceACCESS_TOKEN_SECRETREFRESH_TOKEN_SECRETJWT_ISSUERJWT_AUDIENCEDOCS_ENABLED=true
On Railway, define DATABASE_URL and REDIS_URL on the auth-api service itself by referencing the backing services, rather than assuming those values are shared automatically across services.
TRUST_PROXY=1 is recommended for Railway because the service sits behind a proxy.
METRICS_ENABLED=false is the safer production default unless the metrics route stays private or is protected with METRICS_AUTH_TOKEN.
Create a dedicated GitHub Environment for each deploy target:
productionfor release-driven deploysstaging,homolog, or another non-production environment for manual deploys
Store deployment credentials as environment secrets on each GitHub Environment rather than as broad repository secrets whenever possible.
Each deploy environment needs:
RAILWAY_TOKEN: Railway account or project token used by GitHub ActionsRAILWAY_PROJECT_ID: target Railway project identifierRAILWAY_SERVICE: target Railway service name or identifier for the APIRAILWAY_ENVIRONMENT: target Railway environment name or identifierRAILWAY_PUBLIC_URL: public HTTPS base URL used by smoke checks, for examplehttps://auth-api-production.up.railway.app
Production expectations:
RAILWAY_PUBLIC_URLis required- the URL must belong to the same Railway service targeted by
RAILWAY_SERVICE - GitHub Environment protection rules should require deliberate approval before deploy
Non-production expectations:
RAILWAY_PUBLIC_URLis strongly recommended if the environment is publicly reachable- if the URL is omitted, the deploy may proceed but post-deploy smoke validation is skipped for that non-production environment only
RAILWAY_PUBLIC_URL must belong to the same Railway service targeted by RAILWAY_SERVICE. If the public URL points to a different service or stale domain, the smoke check will typically return 404 for /health.
The deployment workflow lives in .github/workflows/deploy.yml.
It supports two triggers:
- published GitHub releases for production
- manual dispatch for non-production environments with an optional ref override
The workflow now has four explicit phases:
resolve deployment context- determines the exact ref, GitHub Environment, Railway environment, and smoke requirements for the trigger
verify promotion candidate- checks out the exact ref to be promoted
- runs
npm ci - runs
npm run prisma:generate - runs
npm run lint - runs
npm run typecheck - runs
npm run build - runs
npm run test:coverage - runs
npm run prisma:migrate:deploy - runs
npm run test:integration
deploy- validates environment secrets and smoke prerequisites
- deploys with
railway up
validate deployment- runs
scripts/smoke-production.shwhen a public URL is available - always writes a deployment summary to the workflow run
- runs
The workflow clears the default GitHub Actions CI=true value for the deploy step so Railway waits for the deployment result instead of switching to build-only CI mode.
The Railway CLI version is pinned in the workflow on purpose. Update that version deliberately, in reviewable code, rather than pulling latest during a production promotion.
Concurrency is grouped by environment, not by a single hardcoded production bucket, so staging and production deploy queues remain isolated.
railway.json defines deployment behaviour that should stay versioned with the codebase:
/healthas the service healthcheck pathnpm run prisma:migrate:deployas the pre-deploy migration command- failure-based restart policy
- no application sleep in production
- merge the target branch into
main - confirm
qualityandintegrationare green in CI - create and publish a GitHub release from the exact commit to promote
- let
Deployre-verify that release ref and perform the production deployment - confirm the workflow summary reports successful smoke validation
- optionally perform an additional manual product check against
/docsand/docs.json
- open the
Deployworkflow manually - choose a non-production GitHub Environment such as
stagingorhomolog - provide the ref to verify and deploy
- optionally provide
app_base_urlif the environment has a public URL and you want smoke validation in the same run - wait for the verification, deploy, and validation jobs to complete
Recommended manual configuration in GitHub:
production- required reviewers enabled
- wait timer if you want an additional promotion pause
- environment-scoped secrets configured
stagingorhomolog- separate environment-scoped Railway credentials
- lighter approval rules, or no approval, depending on team size and risk appetite
Branch protection should continue to require the quality and integration jobs from .github/workflows/ci.yml for main.
The public demo is live at https://auth-api-production-a97b.up.railway.app.