TL;DR

Railway is a cloud platform that deploys your backend code from GitHub and keeps it running as a real, persistent server. Unlike Vercel (which is great for frontends and serverless), Railway runs actual long-lived servers — perfect for Express APIs, FastAPI backends, WebSocket apps, and background jobs. Click two buttons to add a PostgreSQL or Redis database. Environment variables are injected automatically. You pay for what you use (roughly $5-20/month for small projects after a $5 free trial credit). When your deployment crashes, 90% of the time it's because your server isn't listening on process.env.PORT.

Why AI Coders Need to Know This

When you build a full-stack app with Claude or Cursor, you end up with two distinct pieces: a frontend (what users see in their browser) and a backend (the server that handles data, authentication, and business logic). These two pieces need to live somewhere on the internet.

Most vibe coders discover quickly that Vercel is amazing for the frontend. But when they try to put their Express backend on Vercel, things get weird. Vercel's serverless functions have a 10-second time limit. They can't hold a persistent database connection efficiently. They don't support WebSockets. They're not the right tool for a real API server.

Railway solves this. It's designed for the thing Vercel doesn't do well: running persistent backend servers that stay on all the time. Here's why Railway has become the go-to platform for vibe coders with backends:

  • It deploys from GitHub like Vercel does. Push code, Railway builds it, it's live. No SSH, no server configuration, no Docker knowledge required (though Docker works great if your AI generates one).
  • It includes databases. Need PostgreSQL? Two clicks. Need Redis? Two clicks. Railway manages them for you — no separate database service to sign up for.
  • It's a real server, not serverless. Your Node.js app stays running. It can hold database connection pools, run background jobs, support WebSockets, and do everything a traditional server does.
  • It auto-detects your framework. Railway looks at your code and figures out how to run it. Node.js, Python, Ruby, Go, Java — it handles them all without configuration files.
  • The logs are right there. When something breaks, you click on your service, open the Logs tab, and see exactly what your server is printing. Debugging on Railway feels like debugging locally.

If you're building anything that has a backend — an Express API, a FastAPI server, a Django app, a Node.js server of any kind — you need to know Railway.

Real Scenario: You Built a Full-Stack App and Need It Live

Let's say you spent a weekend with Claude Code building a task management app. The AI built you:

  • A React frontend (Vite-based, runs on localhost:5173)
  • An Express backend with a REST API (runs on localhost:3001)
  • A PostgreSQL database (running in Docker on your machine)

Everything talks to everything on your laptop. It works great. Now you want to share it — put it on the internet so your friends can actually use it.

Your deployment plan:

  • Frontend → Vercel. Perfect fit. Push to GitHub, connect Vercel, done in 2 minutes.
  • Backend + Database → Railway. That's what this article is about.

You ask your AI:

Your AI Prompt

"I have an Express.js backend that connects to PostgreSQL. I want to deploy it to Railway. What do I need to change in my code, and how do I set up Railway to include the database?"

Your AI will probably give you a solid overview and potentially generate a railway.json or Procfile configuration. Let's look at what those files do and whether you actually need them.

What AI Generated

When you ask an AI to help you deploy to Railway, it might generate a few configuration files. Here are the most common ones and what they're for:

The Procfile (Heroku-style, works on Railway)

# Procfile — tells Railway (and Heroku-style platforms) how to start your app
# This is the simplest way to specify your start command
# Railway also auto-detects this from your package.json "start" script

# "web" means this process receives HTTP traffic
web: node server.js

# If you have a background worker (like a queue processor), add it:
# worker: node worker.js

💡 Do you actually need a Procfile? Usually no. Railway reads your package.json and runs the start script automatically. A Procfile is only needed if you have multiple process types (like a web server AND a background worker) or if you want to be explicit. If your AI generates one for a basic Express app, you can delete it and Railway will still deploy fine.

A railway.json Configuration File

// railway.json — optional Railway configuration
// Most projects don't need this file at all
{
  // Override the default build command
  // (Railway auto-detects: "npm install && npm run build" for Node.js)
  "build": {
    "builder": "NIXPACKS",
    "buildCommand": "npm install && npm run build"
  },

  // Override the start command
  // (Railway reads "start" script from package.json by default)
  "deploy": {
    "startCommand": "node dist/server.js",

    // Health check: Railway pings this URL to verify the app started
    // If it doesn't respond 200 OK, Railway considers the deploy failed
    "healthcheckPath": "/health",
    "healthcheckTimeout": 300,

    // How many times to retry a failed deploy before giving up
    "numReplicas": 1
  }
}

💡 Same story as vercel.json: Railway auto-detects most things. You only need railway.json when you want to override Railway's defaults — custom build steps, a health check endpoint, or a non-standard start command. For a basic Express app that has a "start": "node server.js" in package.json, you need zero configuration files.

The One Code Change You Actually Must Make

This is the most important thing your AI might generate — and might not explain clearly enough. Your server needs to listen on the port Railway tells it to use, not a hardcoded number:

// server.js — the critical change for Railway deployment

// ❌ This breaks on Railway (port 3001 is hardcoded)
app.listen(3001, () => {
  console.log('Server running on port 3001')
})

// ✅ This works on Railway (reads the port from environment)
// Railway sets the PORT environment variable automatically
// When running locally, it falls back to 3001
const PORT = process.env.PORT || 3001
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`)
})

When you ask Claude to build an Express server, it will usually write process.env.PORT || 3000. That's correct. But if it hardcodes the port, or if you hardcoded it yourself, Railway won't be able to route traffic to your app and it'll appear to crash on startup.

Understanding Each Part

Railway organizes everything around a concept called a project. Inside a project, you have services. Each service is one piece of your app: your backend, your database, your frontend, a background worker. They all live together and can talk to each other on a private network.

Services: The Building Blocks

When you deploy to Railway, you're creating services. There are two kinds:

  • GitHub-connected services — Railway watches your GitHub repo. When you push code, Railway rebuilds and redeploys automatically. This is where your Express app, Python API, or any application code lives.
  • Database services — Pre-built PostgreSQL, Redis, or MongoDB instances that Railway manages for you. No code required — you just click "Add Database" and it appears.

How Deployment Works: Push to Live in 90 Seconds

  1. You push to GitHub. Railway detects the push via a webhook.
  2. Railway builds your code. It uses a tool called Nixpacks that automatically detects your language (Node.js, Python, etc.), installs dependencies, and runs your build command.
  3. Railway starts your server. It runs your start command, assigns a port via the PORT environment variable, and waits for your server to start accepting connections.
  4. Railway gives you a public URL. Something like your-app.up.railway.app. Your server is now reachable from the internet.
  5. Your server stays running. Unlike serverless functions, your process stays alive. It handles the next request, and the next one, without cold starts or shutdown between requests.

Compare this to a VPS: on a VPS you'd SSH in, install Node.js, set up nginx as a reverse proxy, configure process management with pm2, and hope you didn't miss a step. Railway does all of that for you invisibly.

Adding PostgreSQL in Two Clicks

This is where Railway genuinely shines. Inside your Railway project:

  1. Click "New Service"
  2. Click "Database"
  3. Select "PostgreSQL"
  4. Railway creates a Postgres instance and adds a DATABASE_URL variable to your project automatically

That DATABASE_URL is a connection string that looks like:

postgresql://postgres:randompassword@containers-us-west-42.railway.app:5432/railway

Your Express app can connect to it like this:

// server.js — connecting to Railway's PostgreSQL

// When you ask Claude to add database support, it typically generates this:
import { Pool } from 'pg'

// Railway automatically injects DATABASE_URL as an environment variable
// You don't need to set this yourself — Railway does it when you add a Postgres service
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,

  // Required for Railway's hosted Postgres: SSL is enabled
  ssl: {
    rejectUnauthorized: false
  }
})

// Test the connection on startup
pool.query('SELECT NOW()', (err, res) => {
  if (err) {
    console.error('Database connection failed:', err)
  } else {
    console.log('Database connected:', res.rows[0].now)
  }
})

⚠️ The SSL gotcha: When you ask Claude to write database connection code for a local Postgres setup, it won't include SSL configuration because your local database doesn't need it. Railway's hosted Postgres requires SSL. If you see an error like no pg_hba.conf entry for host or SSL connection required, add ssl: { rejectUnauthorized: false } to your connection config. Your AI might miss this — now you know to watch for it.

Environment Variables on Railway

Railway handles environment variables the same way Vercel does — you add them in the dashboard, they get injected into your running app. But Railway has one extra feature that's genuinely helpful: service linking.

When you add a PostgreSQL database service to your project, Railway can automatically link the database's connection details to your backend service. Instead of copying and pasting the DATABASE_URL yourself, Railway injects it. You reference it in your code as process.env.DATABASE_URL and it just works.

For your own secrets (Stripe keys, JWT secrets, third-party API keys), you add them manually in the Variables tab of each service:

  • Click on your service in the Railway dashboard
  • Go to the Variables tab
  • Click "New Variable" and add your key-value pairs
  • Railway restarts your service to pick up the new variables

Logs and Monitoring

Every time your Node.js code runs console.log(), console.error(), or throws an exception, Railway captures it. To see your logs:

  1. Open your Railway project
  2. Click on the service
  3. Click the Logs tab

Logs are real-time — you can watch them as requests come in. This makes Railway feel like debugging locally. You can also see deployment logs (the build output from when your code was built and started) separately from runtime logs (what your server prints while running).

This is important because Railway gives you two different places to look when things go wrong:

  • Build logs — for when your code fails to deploy (compilation errors, missing dependencies)
  • Runtime logs — for when your code deploys but crashes or behaves wrong while running

Custom Domains

Your Railway service starts with a generated URL like your-project-production.up.railway.app. To use your own domain:

  1. Go to your service's Settings tab
  2. Click "Custom Domain"
  3. Enter your domain (e.g., api.yourapp.com)
  4. Railway gives you a CNAME record to add at your domain registrar
  5. Add the CNAME, wait for DNS propagation (5-30 minutes)
  6. Railway provisions a free SSL certificate automatically

Most vibe coders use a subdomain like api.yourapp.com for the Railway backend and yourapp.com for the Vercel frontend. This pattern works cleanly and keeps the two platforms separate.

Railway vs Vercel vs Netlify vs Render

There's a platform for every use case. Here's an honest breakdown of the four platforms vibe coders encounter most — and when to reach for each one:

Railway

Best for: Full-stack apps, Express/FastAPI backends, apps that need databases

  • ✅ Real persistent servers (not serverless)
  • ✅ One-click PostgreSQL, Redis, MongoDB
  • ✅ WebSockets and long-running processes
  • ✅ Auto-detects Node, Python, Ruby, Go, Java
  • ✅ Great logs — feels like debugging locally
  • ✅ Multiple services per project (frontend + backend + DB)
  • ⚠️ No permanent free tier — usage-based after $5 trial
  • ⚠️ No global CDN (single-region for servers)
  • ⚠️ Not optimized for static content delivery

Pricing: $5 trial credit, then ~$0.000463/vCPU-minute + $0.000231/GB-minute RAM

Vercel

Best for: Next.js apps, React frontends, JAMstack sites with API routes

  • ✅ Created Next.js — deepest integration possible
  • ✅ Global edge network (fast everywhere)
  • ✅ Preview deployments on every pull request
  • ✅ Generous free tier (no credit card needed)
  • ✅ Automatic SSL and CDN for static files
  • ⚠️ Serverless only — no persistent servers
  • ⚠️ 10-second function timeout on free tier
  • ⚠️ No native database hosting
  • ⚠️ No WebSocket support without workarounds

Pricing: Free Hobby tier, Pro at $20/month per member

Netlify

Best for: Static sites, Astro, Gatsby, Hugo, simpler frontends

  • ✅ Great for pure static sites
  • ✅ Built-in form handling and split testing
  • ✅ Easy to use, excellent docs
  • ✅ Free tier with 100GB bandwidth
  • ⚠️ Next.js integration lags behind Vercel
  • ⚠️ No native database hosting
  • ⚠️ Serverless functions only (10s limit on free)

Pricing: Free tier, Pro at $19/month per member

Render

Best for: Similar to Railway — persistent servers, Docker, background workers

  • ✅ Free tier for web services (spins down after inactivity)
  • ✅ PostgreSQL and Redis hosting
  • ✅ Docker support
  • ✅ Background workers and cron jobs
  • ⚠️ Free tier has 30-second cold start after inactivity
  • ⚠️ Less polished dashboard than Railway
  • ⚠️ Slower builds than Railway

Pricing: Free tier available, paid from $7/month

📋 Quick decision guide:

  • 🔵 Next.js app → Vercel (built for it)
  • 🟠 Express or Python API → Railway (needs a persistent server)
  • 🟢 Static site or portfolio → Vercel or Netlify (both free, both fast)
  • 🟠 Full-stack with WebSockets → Railway (Vercel can't do persistent connections)
  • 🟠 App with a database → Railway (database built in) or Vercel + Supabase
  • 🟢 Free backend needed → Render (has a free tier, but cold starts)
  • 🔵 Frontend + backend together → Railway for both, or Vercel (frontend) + Railway (backend)

What AI Gets Wrong About Deployment on Railway

AI tools write good code. They're less good at understanding what a specific deployment platform needs. Here are the mistakes you'll see, with exactly how to spot and fix them:

1. Hardcoding the Port Number

// ❌ AI generates this frequently — it works locally, breaks on Railway
const express = require('express')
const app = express()

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

// Railway error: App never starts, deployment times out
// Railway assigns a random port via the PORT env variable
// If your app doesn't use it, Railway can't route traffic to you

// ✅ The correct version — works everywhere
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`)
})

If you see this error in your Railway logs: Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch — hardcoded port is almost certainly the cause.

2. Forgetting SSL for the Database Connection

// ❌ AI writes this for local development
const pool = new Pool({
  connectionString: process.env.DATABASE_URL
  // No SSL config — works locally, fails on Railway
})

// Railway error: "no pg_hba.conf entry for host..." or "SSL required"

// ✅ Add SSL for Railway's hosted Postgres
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: {
    rejectUnauthorized: false  // Required for Railway's Postgres
  }
})

Your AI will get this wrong sometimes — here's how to spot it: your deployment succeeds (the server starts) but any request that touches the database returns a 500 error. Check your runtime logs and you'll see a Postgres SSL connection error.

3. Assuming the Environment Is Like Your Laptop

// ❌ AI writes code that references local file paths or local services
import fs from 'fs'

// Reading a JSON file that only exists on your laptop
const config = JSON.parse(fs.readFileSync('./config/local-settings.json', 'utf-8'))

// Railway error: ENOENT: no such file or directory, open './config/local-settings.json'
// The file doesn't exist in your Git repo (it was in .gitignore)

// ✅ Put config in environment variables instead
// In Railway Variables tab, add: FEATURE_FLAG_X=true
const config = {
  featureFlagX: process.env.FEATURE_FLAG_X === 'true'
}

4. Missing the Start Script in package.json

// ❌ package.json without a start script — Railway doesn't know how to run your app
{
  "name": "my-api",
  "version": "1.0.0",
  "scripts": {
    "dev": "nodemon server.js"
    // No "start" script!
  }
}

// Railway error: Build succeeded but app fails to start
// Railway looks for "npm start" — if it's not there, deployment fails

// ✅ Always include a start script for production
{
  "name": "my-api",
  "version": "1.0.0",
  "scripts": {
    "dev": "nodemon server.js",
    "start": "node server.js"   // Railway uses this
  }
}

5. Using devDependencies for Production Packages

// ❌ AI sometimes puts runtime packages in devDependencies
{
  "dependencies": {
    "express": "^4.18.0"
  },
  "devDependencies": {
    "pg": "^8.11.0",        // ← should be in dependencies!
    "dotenv": "^16.0.0"     // ← Railway doesn't install devDependencies in production
  }
}

// Railway error: Cannot find module 'pg'
// Railway runs "npm install --production" which skips devDependencies

// ✅ Move runtime packages to dependencies
{
  "dependencies": {
    "express": "^4.18.0",
    "pg": "^8.11.0",
    "dotenv": "^16.0.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.0"     // Dev tools only here
  }
}

📋 The checklist your AI won't give you: Before every Railway deploy, verify: (1) process.env.PORT — not a hardcoded number. (2) npm start script exists in package.json. (3) All runtime packages in dependencies, not devDependencies. (4) Database connection uses SSL if connecting to Railway Postgres. (5) Secrets are in Railway Variables, not hardcoded in code. Five checks, five minutes, prevents 95% of deployment failures.

How to Debug Railway Deployment Issues with AI

Railway tells you exactly what's wrong — you just need to know where to look and how to ask your AI about it.

Step 1: Find the Right Logs

Railway has two kinds of logs, and they tell you different things:

  • Build logs — What happened when Railway tried to install dependencies and compile your code. If the problem is here, you'll see npm install errors, missing packages, or compilation failures.
  • Deploy/Runtime logs — What your running server is printing. If the problem is here, you'll see your own console.log output, database connection errors, and crash stack traces.

Click on the failing deployment in your Railway dashboard, then switch between the Build and Deploy tabs to find where things went sideways.

Common Errors and What They Mean in Plain English

"Error R10 (Boot timeout) — Web process failed to bind to $PORT within 60 seconds"
Plain English: Your server didn't start listening for connections in time. Almost always means you hardcoded the port number instead of using process.env.PORT. Fix it as shown above and redeploy.

"Cannot find module 'express'" (or any package name)
Plain English: Railway can't find an npm package it needs. Either the package is in devDependencies instead of dependencies, or your package.json doesn't include it at all. Move the package to dependencies and push a new commit.

"no pg_hba.conf entry for host" or "SSL connection required"
Plain English: Your database connection doesn't have SSL enabled, but Railway's PostgreSQL requires it. Add ssl: { rejectUnauthorized: false } to your connection config.

"Process exited with code 1" with no other error
Plain English: Your app crashed immediately after starting. Switch to the Deploy tab, look at the last lines before the crash. It's usually an unhandled exception on startup — often a missing environment variable or a bad database connection.

Debug Prompt Templates

Debug Prompt: Deployment Crash

"My Railway deployment is failing with this error in the build/deploy logs: [paste exact error text]. Here's my server.js and package.json. What's causing it and how do I fix it?"

Debug Prompt: App Deploys but Doesn't Work

"My Railway deployment succeeds (green) but when I hit my API at [URL]/endpoint, I get a 500 error. Here are the last 20 lines from my Railway logs: [paste logs]. What's going wrong?"

Debug Prompt: Database Not Connecting

"My Express app can't connect to the Railway PostgreSQL database. The error is: [paste error]. Here's my database connection code. I'm using Railway's DATABASE_URL environment variable. What's missing?"

⚠️ When sharing logs with AI: Railway logs may contain your database URL or other secrets if your code accidentally prints them. Scan for anything that looks like a password or API key before pasting. Replace real values with placeholders like [DATABASE_URL]. Your AI doesn't need the actual secrets to help debug.

The "Works Locally, Broken on Railway" Checklist

  1. Port binding: Is your server using process.env.PORT?
  2. Start script: Does your package.json have a "start" script?
  3. Dependencies: Are all runtime packages in dependencies (not devDependencies)?
  4. Environment variables: Are all your secrets in Railway's Variables tab?
  5. Database SSL: Does your Postgres connection have ssl: { rejectUnauthorized: false }?
  6. Node version: Is Railway using the same Node.js version as your local machine? Check Settings → Environment → Node version.
  7. Local files: Is your code reading files that exist on your laptop but aren't committed to Git?
  8. .gitignore: Is the file you need (a config, a schema, a migration) accidentally ignored?

Your First Railway Deployment, Step by Step

Let's walk through deploying a Node.js/Express app to Railway from scratch. This takes about 10 minutes.

Step 1: Make Sure Your Code Is Ready

Before touching Railway, verify two things in your project:

// Check 1: package.json has a start script
{
  "scripts": {
    "start": "node server.js",   // ← This must exist
    "dev": "nodemon server.js"
  }
}

// Check 2: server listens on process.env.PORT
const PORT = process.env.PORT || 3000
app.listen(PORT, () => console.log(`Running on port ${PORT}`))

Step 2: Push to GitHub

Railway deploys from GitHub. If your project isn't there yet, ask your AI to help:

AI Prompt

"Help me push my Express.js project to a new GitHub repository. I have Git installed but I've never done this before. Walk me through each command."

Make sure your .gitignore includes node_modules/ and .env (or .env.local). Never commit your secrets to Git.

Step 3: Create a Railway Account and Project

  1. Go to railway.app and sign up with GitHub
  2. Click "New Project"
  3. Select "Deploy from GitHub repo"
  4. Choose your repository
  5. Railway detects Node.js and starts building

Step 4: Add Environment Variables

Before your first deployment succeeds, add your environment variables:

  1. Click on your service in the Railway project
  2. Go to the Variables tab
  3. Add every key from your local .env file (names AND values — this is safe because Railway encrypts them)
  4. Railway automatically restarts your service after you save

Step 5: Add PostgreSQL (if you need a database)

  1. In your project view, click "New Service"
  2. Click "Database""PostgreSQL"
  3. Railway creates the database and adds DATABASE_URL to your project's shared variables
  4. Click on your backend service → Variables — you should see DATABASE_URL available
  5. Reference it in your code as process.env.DATABASE_URL

Step 6: Get Your Public URL

Railway doesn't give you a public URL by default — you have to enable it:

  1. Click on your service
  2. Go to the Settings tab
  3. Under "Networking", click "Generate Domain"
  4. Railway gives you a .up.railway.app URL

That's your live API. Hit it from your browser to confirm it's working. Every future push to your main branch will auto-redeploy.

What to Learn Next

Railway is the bridge between "it works on my machine" and "it's on the internet." Here are the concepts that connect directly to what you just learned:

Frequently Asked Questions

Is Railway free?

Railway gives new accounts a one-time $5 trial credit — enough to test your deployment and run small projects for a few weeks. After that, you pay for what you use. A small Node.js API server with a PostgreSQL database typically costs $5-20/month depending on traffic and usage. There's no permanent free tier like Vercel's Hobby plan. The pricing is usage-based: you're charged per CPU minute and per GB of RAM minute your services consume. For most AI-built side projects, costs stay low — the surprise comes when you leave heavy background processes running or forget to pause unused services.

What's the difference between Railway and Vercel?

Vercel is designed for frontends and serverless API routes — it's the best choice for Next.js apps, React frontends, and JAMstack sites. It runs your backend code as short-lived serverless functions that spin up on demand and shut down after each request. Railway runs actual persistent servers — your process stays alive, holds open database connections, can process WebSocket connections, and runs background jobs without timeout limits. If your AI built you a Next.js app with API routes, Vercel is usually simpler. If your AI built you a standalone Express or FastAPI server, Railway is the right home for it.

Does Railway support PostgreSQL?

Yes, and it's one of Railway's best features. You can add a PostgreSQL database to your project with two clicks — no separate account, no separate service to sign up for, no configuration files. Railway creates the database, manages backups, and automatically makes a DATABASE_URL environment variable available to your services. Redis is also available as a one-click add-on. The database runs as its own service inside your Railway project, on a private network — your backend can connect to it directly without exposing the database to the public internet.

Why does my Railway deployment keep crashing?

The most common cause is your server not listening on process.env.PORT. Railway assigns a random port and passes it as the PORT environment variable — your app must use it. The error looks like: Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds. Fix: change app.listen(3000) to app.listen(process.env.PORT || 3000). The second most common cause is a missing environment variable — your API key or database URL isn't set in Railway's Variables tab. Check the Deploy logs in your Railway dashboard for the exact error message — it almost always tells you what's wrong.

Can I deploy both my frontend and backend on Railway?

Yes. Railway lets you run multiple services in one project. You can have your Express backend as one service, your React frontend as another, and a PostgreSQL database as a third — all on a private network. Many vibe coders use a hybrid approach: Railway for the backend and database, and Vercel for the frontend. The hybrid gives you Vercel's fast global CDN for the React/Next.js frontend while keeping Railway's persistent servers for the API. Both approaches work well — the choice comes down to whether you want everything in one dashboard or are happy managing two platforms.