Skip to content

ADR-23: Railway IaC Deployment

한국어 버전

DateAuthorRepos
2026-02-02@KubrickCodeworker, web, infra

Status

Accepted - Complements ADR-06: PaaS-First Infrastructure and ADR-22: Scheduler Removal and Railway Cron Migration

Context

Problem Statement

Railway platform integration initially used the GitHub App automatic deployment feature, which presented several operational challenges:

  1. Manual Dashboard Configuration: Each service required manual setup through Railway's web dashboard after GitHub App integration
  2. Non-Reproducible Deployments: Service recreation required re-doing all manual configurations with no history tracking
  3. BuildArgs Limitation: Railway's railway.json schema does not officially support buildArgs for Docker builds
  4. Environment Variable Rate Limiting: Multiple sequential railway variables --set CLI calls triggered Railway API rate limits

Why Change Was Required

IssueImpactFrequency
Manual Dashboard setupDeployment drift, onboarding frictionEvery new service
Non-reproducibilityDisaster recovery riskService recreation
BuildArgs unsupportedCannot share DockerfileBuild configuration
Rate limiting on variablesFailed deployments, retry overheadEvery deployment

Decision

Adopt GitHub Actions with Railway CLI for Infrastructure as Code (IaC) deployment, using per-service Dockerfiles and railway.json configuration files.

Configuration Structure

infra/
├── analyzer/
│   ├── Dockerfile          # Service-specific Dockerfile
│   └── railway.json        # Service configuration with $schema
├── spec-generator/
│   ├── Dockerfile
│   └── railway.json
├── retention-cleanup/
│   ├── Dockerfile
│   └── railway.json
└── web-backend/
    ├── Dockerfile
    └── railway.json

Key Patterns

1. Per-Service Dockerfile (avoids buildArgs limitation):

Each service has its own Dockerfile instead of a shared Dockerfile with build arguments:

dockerfile
# infra/analyzer/Dockerfile
FROM golang:1.22-alpine AS builder
# ... service-specific build steps

2. railway.json Configuration:

json
{
  "$schema": "https://railway.com/railway.schema.json",
  "build": {
    "builder": "DOCKERFILE",
    "dockerfilePath": "infra/analyzer/Dockerfile"
  },
  "deploy": {
    "restartPolicyType": "ON_FAILURE",
    "restartPolicyMaxRetries": 3
  }
}

3. Cron Service Configuration (for scheduled jobs):

json
{
  "$schema": "https://railway.com/railway.schema.json",
  "build": {
    "builder": "DOCKERFILE",
    "dockerfilePath": "infra/retention-cleanup/Dockerfile"
  },
  "deploy": {
    "cronSchedule": "0 3 * * *",
    "restartPolicyType": "NEVER"
  }
}

4. GitHub Actions Matrix Deployment:

yaml
deploy:
  strategy:
    matrix:
      service: [analyzer, spec-generator, retention-cleanup]
    fail-fast: true
  steps:
    - name: Install Railway CLI
      run: npm install -g @railway/cli
    - name: Deploy Service
      run: |
        cp infra/${{ matrix.service }}/railway.json railway.json
        railway up --service ${{ matrix.service }} --detach
        rm railway.json
      env:
        RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}

Options Considered

Option A: GitHub Actions + Railway CLI (Selected)

Deploy via GitHub Actions workflow using Railway CLI with per-service configuration files.

Pros:

  • Version controlled configuration in Git (code review, history)
  • Reproducible deployments from any environment
  • Per-service Dockerfile eliminates buildArgs limitation
  • Matrix strategy enables parallel service deployments
  • $schema provides IDE autocompletion and validation
  • Consolidated API calls reduce rate limiting issues

Cons:

  • Configuration duplication across service Dockerfiles
  • Deployment tied to GitHub Actions availability
  • Multiple railway.json files to maintain

Option B: Railway GitHub App Integration (Previous)

Automatic deployment triggered by GitHub push events via Railway's native integration.

Pros:

  • Zero CI/CD setup required
  • Automatic deployment on push
  • Simple mental model

Cons:

  • Manual Dashboard configuration required per service
  • Configuration not version controlled
  • Environment drift between staging and production
  • Same buildArgs limitation applies

Option C: Pulumi/Terraform with Railway Provider

Use infrastructure-as-code tools with Railway's provider.

Pros:

  • Full declarative infrastructure management
  • State tracking and drift detection
  • Cross-provider resource management

Cons:

  • Additional tooling complexity
  • State file management overhead
  • Over-engineering for single-platform deployment
  • Railway provider may have feature gaps

Implementation

Deployment Workflow

  1. Build Phase: Railway CLI reads railway.json from repository root
  2. Config Copy: Workflow copies service-specific config to root
  3. Deploy: railway up executes with service name
  4. Cleanup: Remove copied config file

Environment Variables

  • Project-level: Shared across services (DATABASE_URL, etc.)
  • Service-level: Set via consolidated workflow step to avoid rate limiting

Railway Project Token

Uses Project Token (not User Token) for CI/CD:

yaml
env:
  RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}

No railway link command needed with Project Token.

Consequences

Positive

Reproducibility:

  • All deployment configuration version-controlled
  • Any service can be recreated from repository alone
  • Deployment changes visible in pull requests

Developer Experience:

  • Schema validation provides IDE autocompletion
  • Clear per-service configuration boundaries
  • Familiar Git-based workflow

Operational Reliability:

  • Consolidated API calls avoid rate limiting
  • Matrix strategy enables parallel deployments
  • Clear deployment logs in GitHub Actions

Negative

Configuration Duplication:

  • Per-service Dockerfiles contain similar base layers
  • Minor changes may require updating multiple files
  • Mitigation: Use multi-stage builds with common base image

GitHub Actions Dependency:

  • Deployment requires GitHub Actions runners
  • GitHub outages block automated deployments
  • Mitigation: Railway CLI can be run manually as fallback

Learning Curve:

  • Team must understand railway.json schema
  • GitHub Actions workflow syntax required
  • Mitigation: Centralized workflow with clear documentation
ADRRelationship
ADR-06: PaaS-First InfrastructureFoundation - Railway as PaaS platform
ADR-22: Scheduler RemovalExtension - Railway Cron configuration

References

Open-source test coverage insights