home / skills / chunkytortoise / enterprisehub / railway-deploy

This skill guides you through deploying Python, Node.js, or other backends to Railway with streamlined setup and environment management.

npx playbooks add skill chunkytortoise/enterprisehub --skill railway-deploy

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
17.4 KB
---
name: Railway Deploy
description: This skill should be used when the user asks to "deploy to Railway", "deploy backend", "deploy Python app", "deploy Node.js server", "railway deployment", or mentions Railway hosting workflows.
version: 1.0.0
---

# Deploy to Railway

## Overview

Railway provides modern backend deployment with automatic builds, environment management, and database integration. This skill guides through production-ready Railway deployments for Python, Node.js, and other backend applications.

## Prerequisites Check

### Install Railway CLI
```bash
# Install via npm
npm install -g @railway/cli

# Install via curl (Unix/macOS)
curl -fsSL https://railway.app/install.sh | sh

# Install via Homebrew (macOS)
brew install railway
```

### Authentication
```bash
railway login
```

### Verify Setup
```bash
railway --version
railway whoami
```

## Project Configuration

### Railway Configuration File
Create `railway.json` in project root:

```json
{
  "deploy": {
    "startCommand": "python main.py",
    "healthcheckPath": "/health",
    "healthcheckTimeout": 300,
    "restartPolicyType": "ON_FAILURE",
    "restartPolicyMaxRetries": 10
  }
}
```

### Dockerfile Configuration
```dockerfile
# Dockerfile
FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Copy requirements first for better caching
COPY requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8000/health || exit 1

# Start command
CMD ["python", "main.py"]
```

### Python/FastAPI Configuration
```python
# main.py
import os
import uvicorn
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI(
    title="Railway API",
    description="Production-ready API on Railway",
    version="1.0.0"
)

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # Configure for production
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/health")
async def health_check():
    """Health check endpoint for Railway"""
    return {
        "status": "healthy",
        "service": "railway-api",
        "version": "1.0.0"
    }

@app.get("/")
async def root():
    return {"message": "Hello from Railway!"}

if __name__ == "__main__":
    port = int(os.environ.get("PORT", 8000))
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=port,
        reload=False,
        access_log=True
    )
```

### Node.js/Express Configuration
```javascript
// server.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(helmet());
app.use(cors());
app.use(express.json());

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    service: 'railway-api',
    version: '1.0.0',
    timestamp: new Date().toISOString()
  });
});

// Routes
app.get('/', (req, res) => {
  res.json({ message: 'Hello from Railway!' });
});

// Error handling
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

app.listen(PORT, '0.0.0.0', () => {
  console.log(`Server running on port ${PORT}`);
});
```

## Deployment Workflows

### Initial Project Setup
```bash
# Initialize Railway project
railway init

# Link to existing project
railway link [project-id]

# Create new project
railway new
```

### Environment Configuration
```bash
# Set environment variables
railway variables set NODE_ENV=production
railway variables set DATABASE_URL=${{Postgres.DATABASE_URL}}
railway variables set REDIS_URL=${{Redis.REDIS_URL}}

# List environment variables
railway variables

# Load local environment
railway run --service backend python main.py
```

### Database Integration
```bash
# Add PostgreSQL database
railway add postgres

# Add Redis cache
railway add redis

# Add MongoDB
railway add mongodb

# Check database status
railway status
```

### Deployment Commands
```bash
# Deploy current directory
railway up

# Deploy specific service
railway up --service backend

# Deploy with environment
railway up --environment production

# Deploy from Git
railway up --git-branch main
```

## Environment Management

### Multi-Environment Setup
```bash
# Create environments
railway environment create staging
railway environment create production

# Switch environments
railway environment use staging
railway environment use production

# List environments
railway environment list
```

### Environment Variables Management
```bash
# Service-specific variables
railway variables set --service backend API_KEY=your-key
railway variables set --service worker QUEUE_URL=redis://...

# Environment-specific variables
railway variables set --environment production SECRET_KEY=prod-secret
railway variables set --environment staging SECRET_KEY=staging-secret

# Template variables (shared across services)
railway variables set DATABASE_URL=${{Postgres.DATABASE_URL}}
```

### Secrets Management
```bash
# Add sensitive variables
railway variables set --service backend \
  JWT_SECRET=$(openssl rand -base64 32)

# Use Railway's secret interpolation
railway variables set API_URL="https://${{RAILWAY_PUBLIC_DOMAIN}}/api"
```

## Service Configuration

### Multi-Service Applications
```yaml
# railway.toml
[build]
builder = "NIXPACKS"

[[services]]
name = "backend"
source = "./backend"
[services.build]
buildCommand = "pip install -r requirements.txt"
startCommand = "python main.py"
[services.deploy]
healthcheckPath = "/health"
healthcheckTimeout = 300

[[services]]
name = "worker"
source = "./worker"
[services.build]
buildCommand = "pip install -r requirements.txt"
startCommand = "python worker.py"

[[services]]
name = "frontend"
source = "./frontend"
[services.build]
buildCommand = "npm run build"
startCommand = "npm start"
```

### Database Services
```bash
# PostgreSQL configuration
railway add postgres
railway variables set DATABASE_URL=${{Postgres.DATABASE_URL}}

# Redis configuration
railway add redis
railway variables set REDIS_URL=${{Redis.REDIS_URL}}

# MongoDB configuration
railway add mongodb
railway variables set MONGODB_URI=${{MongoDB.MONGODB_URI}}
```

## CI/CD Integration

### GitHub Actions
```yaml
# .github/workflows/railway-deploy.yml
name: Deploy to Railway

on:
  push:
    branches: [main, staging]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Setup Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'

    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install pytest

    - name: Run tests
      run: pytest

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Install Railway CLI
      run: npm install -g @railway/cli

    - name: Deploy to Railway
      run: railway up --service backend
      env:
        RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
```

### GitLab CI/CD
```yaml
# .gitlab-ci.yml
stages:
  - test
  - build
  - deploy

variables:
  PYTHON_VERSION: "3.11"

test:
  stage: test
  image: python:$PYTHON_VERSION
  script:
    - pip install -r requirements.txt
    - pip install pytest
    - pytest

deploy_staging:
  stage: deploy
  image: node:18
  script:
    - npm install -g @railway/cli
    - railway up --service backend --environment staging
  environment:
    name: staging
  only:
    - staging

deploy_production:
  stage: deploy
  image: node:18
  script:
    - npm install -g @railway/cli
    - railway up --service backend --environment production
  environment:
    name: production
  only:
    - main
  when: manual
```

## Monitoring and Logging

### Application Logging
```python
# logging_config.py
import logging
import sys
from datetime import datetime

# Configure structured logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout)
    ]
)

logger = logging.getLogger(__name__)

# Request logging middleware for FastAPI
from fastapi import Request
import time

async def log_requests(request: Request, call_next):
    start_time = time.time()

    response = await call_next(request)

    process_time = time.time() - start_time
    logger.info(
        f"Request: {request.method} {request.url} "
        f"Status: {response.status_code} "
        f"Time: {process_time:.4f}s"
    )

    return response
```

### Health Monitoring
```python
# health.py
import psutil
import asyncpg
from fastapi import HTTPException

async def check_database_health():
    """Check database connectivity"""
    try:
        conn = await asyncpg.connect(DATABASE_URL)
        await conn.execute("SELECT 1")
        await conn.close()
        return True
    except Exception as e:
        logger.error(f"Database health check failed: {e}")
        return False

async def check_system_health():
    """Check system resources"""
    cpu_percent = psutil.cpu_percent(interval=1)
    memory_percent = psutil.virtual_memory().percent

    return {
        "cpu_usage": cpu_percent,
        "memory_usage": memory_percent,
        "healthy": cpu_percent < 80 and memory_percent < 80
    }

@app.get("/health/detailed")
async def detailed_health_check():
    """Comprehensive health check"""
    db_healthy = await check_database_health()
    system_health = await check_system_health()

    if not db_healthy or not system_health["healthy"]:
        raise HTTPException(status_code=503, detail="Service unhealthy")

    return {
        "status": "healthy",
        "database": db_healthy,
        "system": system_health,
        "timestamp": datetime.utcnow().isoformat()
    }
```

### Railway Logging
```bash
# View logs
railway logs

# Follow logs in real-time
railway logs --follow

# Filter logs by service
railway logs --service backend

# Export logs
railway logs --json > app-logs.json
```

## Database Management

### PostgreSQL Setup
```python
# database.py
import asyncpg
import asyncio
from contextlib import asynccontextmanager

class DatabaseManager:
    def __init__(self, database_url: str):
        self.database_url = database_url
        self.pool = None

    async def connect(self):
        """Create connection pool"""
        self.pool = await asyncpg.create_pool(
            self.database_url,
            min_size=5,
            max_size=20,
            command_timeout=60
        )

    async def disconnect(self):
        """Close connection pool"""
        if self.pool:
            await self.pool.close()

    @asynccontextmanager
    async def get_connection(self):
        """Get database connection from pool"""
        async with self.pool.acquire() as connection:
            yield connection

# Migration script
async def run_migrations():
    """Run database migrations"""
    db = DatabaseManager(DATABASE_URL)
    await db.connect()

    async with db.get_connection() as conn:
        await conn.execute("""
            CREATE TABLE IF NOT EXISTS users (
                id SERIAL PRIMARY KEY,
                email VARCHAR(255) UNIQUE NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );
        """)

    await db.disconnect()
```

### Redis Integration
```python
# redis_client.py
import redis.asyncio as redis
import json

class RedisClient:
    def __init__(self, redis_url: str):
        self.redis_url = redis_url
        self.client = None

    async def connect(self):
        """Connect to Redis"""
        self.client = redis.from_url(
            self.redis_url,
            encoding="utf-8",
            decode_responses=True
        )

    async def disconnect(self):
        """Disconnect from Redis"""
        if self.client:
            await self.client.close()

    async def set_cache(self, key: str, value: dict, ttl: int = 3600):
        """Set cache with TTL"""
        await self.client.setex(key, ttl, json.dumps(value))

    async def get_cache(self, key: str):
        """Get cache value"""
        value = await self.client.get(key)
        return json.loads(value) if value else None

    async def delete_cache(self, key: str):
        """Delete cache key"""
        await self.client.delete(key)
```

## Performance Optimization

### Application Optimization
```python
# performance.py
from functools import wraps
import time
import asyncio

def async_lru_cache(maxsize: int = 128):
    """Async LRU cache decorator"""
    def decorator(func):
        cache = {}
        cache_order = []

        @wraps(func)
        async def wrapper(*args, **kwargs):
            key = str(args) + str(sorted(kwargs.items()))

            if key in cache:
                # Move to end (most recently used)
                cache_order.remove(key)
                cache_order.append(key)
                return cache[key]

            # Execute function
            result = await func(*args, **kwargs)

            # Add to cache
            cache[key] = result
            cache_order.append(key)

            # Maintain maxsize
            while len(cache) > maxsize:
                oldest_key = cache_order.pop(0)
                del cache[oldest_key]

            return result

        return wrapper
    return decorator

# Connection pooling
from sqlalchemy.pool import QueuePool

engine = create_async_engine(
    DATABASE_URL,
    poolclass=QueuePool,
    pool_size=10,
    max_overflow=20,
    pool_recycle=3600,
    pool_pre_ping=True
)
```

### Resource Limits
```yaml
# railway.toml
[deploy]
healthcheckPath = "/health"
healthcheckTimeout = 300
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 3

[build]
builder = "NIXPACKS"

[environment]
NODE_ENV = "production"
PORT = 8000

# Resource constraints
[resources]
memory = "1Gi"
cpu = "500m"
```

## Security Best Practices

### Environment Security
```python
# security.py
import os
from typing import Optional

class SecurityConfig:
    """Security configuration management"""

    def __init__(self):
        self.secret_key = self._get_required_env("SECRET_KEY")
        self.database_url = self._get_required_env("DATABASE_URL")
        self.allowed_origins = self._get_env_list("ALLOWED_ORIGINS")

    def _get_required_env(self, key: str) -> str:
        """Get required environment variable"""
        value = os.getenv(key)
        if not value:
            raise ValueError(f"Required environment variable {key} not set")
        return value

    def _get_env_list(self, key: str, default: Optional[str] = None) -> list:
        """Get environment variable as list"""
        value = os.getenv(key, default or "")
        return [item.strip() for item in value.split(",") if item.strip()]

# CORS configuration
app.add_middleware(
    CORSMiddleware,
    allow_origins=security_config.allowed_origins,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["*"],
)
```

### Input Validation
```python
# validation.py
from pydantic import BaseModel, validator
from typing import Optional
import re

class UserCreate(BaseModel):
    email: str
    password: str
    name: Optional[str] = None

    @validator('email')
    def validate_email(cls, v):
        if not re.match(r'^[^@]+@[^@]+\.[^@]+$', v):
            raise ValueError('Invalid email format')
        return v.lower()

    @validator('password')
    def validate_password(cls, v):
        if len(v) < 8:
            raise ValueError('Password must be at least 8 characters')
        return v
```

## Troubleshooting Common Issues

### Build Failures
```bash
# Check build logs
railway logs --service backend

# Debug build locally
railway run --service backend python main.py

# Clear build cache
railway up --service backend --no-cache
```

### Database Connection Issues
```python
# Connection testing
async def test_database_connection():
    """Test database connectivity"""
    try:
        conn = await asyncpg.connect(DATABASE_URL)
        result = await conn.fetchval("SELECT version()")
        await conn.close()
        print(f"Database connected: {result}")
        return True
    except Exception as e:
        print(f"Database connection failed: {e}")
        return False
```

### Performance Issues
```bash
# Monitor resource usage
railway metrics --service backend

# Check service status
railway status --service backend

# Scale service
railway up --service backend --replicas 3
```

## Additional Resources

### Reference Files
For detailed deployment configurations, consult:
- **`references/railway-configurations.md`** - Comprehensive Railway configuration examples
- **`references/database-setup.md`** - Database integration and management
- **`references/monitoring-setup.md`** - Monitoring and alerting configuration

### Example Files
Working deployment examples in `examples/`:
- **`examples/fastapi-deployment.py`** - Complete FastAPI Railway setup
- **`examples/nodejs-express-deployment.js`** - Node.js Express deployment
- **`examples/docker-configuration.dockerfile`** - Production Dockerfile examples

### Scripts
Deployment utility scripts in `scripts/`:
- **`scripts/deploy-production.sh`** - Automated Railway deployment
- **`scripts/setup-database.py`** - Database initialization script
- **`scripts/health-check.py`** - Comprehensive health monitoring

Deploy robust backend applications with Railway's modern infrastructure and these production-ready configurations.

Overview

This skill guides developers through deploying backend applications to Railway with production-ready defaults for Python (FastAPI) and Node.js (Express). It covers CLI setup, project configuration, environment and secret management, database integration, CI/CD, logging, and basic performance and security settings. Use it to get a repeatable, monitored Railway deployment with health checks and resource constraints.

How this skill works

The skill inspects project needs and provides concrete configuration snippets: railway.json/railway.toml examples, Dockerfile patterns, service definitions, and health endpoints. It shows Railway CLI commands for init, linking, environment and variable management, adding managed databases, and deployment commands. It also supplies CI/CD examples (GitHub Actions, GitLab) and logging/monitoring guidance to validate production readiness.

When to use it

  • You want to deploy a Python FastAPI or Node.js Express backend to Railway.
  • You need quick Railway CLI commands to initialize, link, or deploy a project.
  • You need examples for configuring health checks, Dockerfiles, or railway.toml for multi-service apps.
  • You are integrating managed databases (Postgres, Redis, Mongo) with Railway.
  • You want CI/CD pipelines that deploy to Railway after testing.

Best practices

  • Add a /health endpoint and configure railway healthcheckPath with timeouts and restart policy.
  • Store secrets with railway variables and use service/environment scoping, not committing secrets to source control.
  • Use connection pooling and async clients for databases to avoid connection exhaustion on Railway.
  • Set resource constraints in railway.toml and tune pool sizes to match allocated memory/CPU.
  • Ship structured logs to stdout and use railway logs --follow for real-time troubleshooting.

Example use cases

  • Deploy a FastAPI app: Dockerfile, main.py health endpoint, railway.json startCommand and railway up.
  • Deploy a Node.js Express server with helmet, cors, /health endpoint, and railway variables for NODE_ENV and PORT.
  • Create multi-service projects (backend, worker, frontend) with railway.toml service sections and per-service environment variables.
  • Add Postgres and Redis through railway add and inject DATABASE_URL/REDIS_URL via railway variables.
  • Automate deployments with GitHub Actions that run tests then call railway up using a RAILWAY_TOKEN secret.

FAQ

Do I need the Railway CLI to deploy?

Yes. Install the Railway CLI (npm, curl, or Homebrew) and authenticate with railway login to initialize, link, and run deployments from your machine or CI.

How do I manage environment-specific secrets?

Use railway variables with --environment or --service flags to scope secrets. For CI, store RAILWAY_TOKEN in your repository secrets and use it when running railway up.