Railway deploys any Dockerfile or Nixpacks-detected stack instantly — railway init creates a project, railway up deploys from the current directory. railway.toml configures build and start: [build] builder = "NIXPACKS", [deploy] startCommand = "npm start", [[deploy.healthchecks]] path = "/api/health". Environment variables: set in the Railway dashboard or railway variables set KEY=VALUE. Shared variables: a shared service exposes vars like ${{Postgres.DATABASE_URL}} to other services in the project. Database provisioning: click “Add Service → Database → PostgreSQL” in the dashboard — Railway provisions a managed Postgres and injects DATABASE_URL. Same for Redis. Private networking: services in the same project communicate on SERVICE_NAME.railway.internal on port PORT — no public internet hop. GitHub integration: connect a repo and every push to main triggers a deploy. PR environments: every pull request gets its own deploy with a unique URL. railway logs streams live logs, railway shell opens a console. Multi-service: define multiple services in one project, each with its own repo/branch or railway.toml. Crons: in the Railway dashboard, create a Cron Service with a schedule and command. Claude Code generates Railway railway.toml, GitHub Actions workflows, and multi-service project configurations.
CLAUDE.md for Railway
## Railway Stack
- CLI: railway (install: npm i -g @railway/cli)
- Init: railway init — links current dir to Railway project
- Deploy: railway up — deploys current directory
- Env vars: railway variables set KEY=VALUE — or set in dashboard, referenced as ${{SERVICE.KEY}} between services
- Logs: railway logs
- Database URL: injected as DATABASE_URL automatically when Postgres service is added
- Private networking: services communicate via RAILWAY_PRIVATE_DOMAIN or SERVICE_NAME.railway.internal
- Nixpacks: auto-detects Node.js, Python, Go, Rust, etc — no Dockerfile required for common stacks
railway.toml
# railway.toml — Next.js full-stack app configuration
[build]
builder = "NIXPACKS"
buildCommand = "npm run build"
[deploy]
startCommand = "npm start"
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 3
[[deploy.healthchecks]]
type = "HTTP"
path = "/api/health"
# interval, timeout, threshold use Railway defaults
Multi-Service Project Layout
# railway.toml for a web + worker multi-service setup
# Service: web (Next.js)
[build]
builder = "NIXPACKS"
[deploy]
startCommand = "npm start"
[[deploy.healthchecks]]
type = "HTTP"
path = "/api/health"
# worker/railway.toml — background job worker
[build]
builder = "NIXPACKS"
buildCommand = "npm run build:worker"
[deploy]
startCommand = "node dist/worker.js"
restartPolicyType = "ALWAYS"
Environment Variable Patterns
// lib/config.ts — validated Railway environment variables
import { z } from "zod"
const Schema = z.object({
DATABASE_URL: z.string().url(), // from Postgres service
REDIS_URL: z.string().url().optional(), // from Redis service
PORT: z.coerce.number().default(3000),
RAILWAY_ENVIRONMENT: z.string().default("development"),
RAILWAY_GIT_COMMIT_SHA: z.string().optional(),
})
export const config = Schema.parse(process.env)
// Private networking — talk to another Railway service without public internet
// e.g. if "api" service is on port 8080:
// const internalUrl = `http://api.railway.internal:8080`
GitHub Actions + Railway Deploy
# .github/workflows/deploy.yml
name: Deploy to Railway
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Railway CLI
run: npm install -g @railway/cli
- name: Deploy
run: railway up --detach # --detach returns immediately after deploy starts
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
Health Check and Status Endpoints
// app/api/health/route.ts — Railway health check endpoint
import { NextResponse } from "next/server"
export const dynamic = "force-dynamic"
export async function GET() {
const checks: Record<string, "ok" | "error"> = {}
// Check database connection
try {
const { db } = await import("@/lib/db")
await db.execute("SELECT 1")
checks.database = "ok"
} catch {
checks.database = "error"
}
const healthy = Object.values(checks).every((v) => v === "ok")
return NextResponse.json(
{
status: healthy ? "ok" : "degraded",
checks,
version: process.env.RAILWAY_GIT_COMMIT_SHA?.slice(0, 7) ?? "local",
environment: process.env.RAILWAY_ENVIRONMENT ?? "development",
timestamp: new Date().toISOString(),
},
{ status: healthy ? 200 : 503 },
)
}
Prisma with Railway Postgres
// lib/db.ts — Prisma + Railway Postgres with connection pooling
import { PrismaClient } from "@prisma/client"
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
export const db =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.RAILWAY_ENVIRONMENT === "development" ? ["query"] : ["error"],
datasources: {
db: {
url: process.env.DATABASE_URL, // injected by Railway Postgres service
},
},
})
if (process.env.RAILWAY_ENVIRONMENT !== "production") globalForPrisma.prisma = db
Railway Cron Service
// scripts/daily-report.ts — run as Railway Cron on schedule
// Set in Railway dashboard: Cron → startCommand: "npx tsx scripts/daily-report.ts"
// Schedule: "0 8 * * *" (8am UTC daily)
import { db } from "../lib/db"
async function main() {
console.log("[cron] Starting daily report", new Date().toISOString())
const stats = await db.order.groupBy({
by: ["status"],
_count: { id: true },
_sum: { amount: true },
where: {
createdAt: {
gte: new Date(Date.now() - 86400_000), // last 24h
},
},
})
console.log("[cron] Daily stats:", JSON.stringify(stats, null, 2))
// Send to Slack, email, etc.
process.exit(0)
}
main().catch((err) => { console.error(err); process.exit(1) })
For the Fly.io alternative when needing Dockerfile-native deployments with anycast global edge routing, Machines API for dynamic VM provisioning, SSH access to running containers, and lower-latency serving from edge regions — Fly.io gives more infrastructure control while Railway is the fastest path from code to deployed URL for teams who want managed hosting without touching Dockerfiles, see the Fly.io guide. For the Render alternative when needing a similar PaaS experience but with static site hosting, background workers, and managed services in one unified pricing model, or when Infrastructure-as-Code via render.yaml in the repo is preferred — Render focuses on render.yaml declarative config while Railway focuses on GUI-first speed with strong GitHub integration and one-click database provisioning, see the Render guide. The Claude Skills 360 bundle includes Railway skill sets covering multi-service projects, Postgres integration, and CI/CD. Start with the free tier to try Railway deployment generation.