Skip to content

Railway: The Developer-First Deployment Platform

Complete guide to Railway—deploy apps, databases, and services with zero configuration

TypeScript Masterclass

AVAILABLE NOW

Railway is the easiest deployment platform I’ve used. No Docker knowledge required. No configuration files. No complex setup. Connect your GitHub repo, and it deploys. That’s it.

I’ve been using Railway for some of my projects for a long time. It’s my go-to when I want to ship fast without thinking about infrastructure. This guide covers everything you need to know about Railway.

What is Railway?

Railway is a deployment platform designed for developers who want to focus on building, not DevOps. It’s like Heroku, if you’re an old timer like me, but modern, faster, and more affordable.

You can deploy from GitHub with zero configuration. Every pull request gets an instant preview deployment. PostgreSQL, MySQL, Redis, and MongoDB are all included—just one click to add them. Environment variables and secrets management is built-in, automatic HTTPS works out of the box, and custom domains are easy to set up. The pricing is fair and usage-based, so you only pay for what you use.

I use Railway for web applications (Node.js, Python, Go, Ruby), APIs and backends, full-stack apps, databases, cron jobs, and any service that needs persistence.

I wouldn’t use it for static sites though—Netlify is better for that. And for massive enterprise apps, AWS or GCP have more enterprise-grade features that might be necessary.

Why I like Railway

Most apps just work with zero config. Railway detects your framework automatically and deploys it correctly. I’ve deployed Node.js, Python, and Go apps without writing a single configuration file.

It’s fast. From git push to deployed takes under a minute. The dashboard is beautiful and intuitive—everything is where you expect it to be.

Databases are included, which is huge. PostgreSQL, MySQL, Redis, MongoDB—one click to add, and they’re instantly connected to your app. No need to sign up for separate services or manage connection strings manually.

Preview deployments are automatic. Every PR gets its own URL, which is perfect for testing changes before merging to production.

The pricing is fair. You pay $5/month and get $5 of usage credit. No minimum spend, no waste. You’re not paying for resources you don’t use.

I’ve never had unexpected downtime with Railway. It just works, which is exactly what I want from a deployment platform.

Getting started

Sign up

Go to railway.app and sign up with GitHub. That’s it. No credit card required to start.

Install CLI (optional)

The CLI is optional but useful:

# Mac/Linux
curl -fsSL https://railway.app/install.sh | sh

# Windows
iwr https://railway.app/install.ps1 | iex

# npm
npm i -g @railway/cli

Login:

railway login

Deploy your first app

Let’s deploy a Node.js app.

Create a simple app

mkdir my-app
cd my-app
npm init -y
npm install express

Create index.js:

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

app.get('/', (req, res) => {
  res.json({ 
    message: 'Hello from Railway!',
    environment: process.env.RAILWAY_ENVIRONMENT || 'local'
  });
});

app.get('/health', (req, res) => {
  res.json({ status: 'ok', uptime: process.uptime() });
});

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

Update package.json:

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}

Push to GitHub

git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/yourusername/my-app.git
git push -u origin main

Deploy on Railway

Option 1: Dashboard

  1. Go to railway.app
  2. Click “New Project”
  3. Choose “Deploy from GitHub repo”
  4. Select your repository
  5. Click “Deploy”

Option 2: CLI

railway init
railway up

That’s it. Railway:

  1. Detects Node.js
  2. Runs npm install
  3. Runs npm start
  4. Deploys your app
  5. Gives you a URL: https://my-app-production.up.railway.app

No configuration needed.

Understanding Railway projects

A Railway project contains:

Each service gets:

Adding a database

Click “New” → “Database” → Choose your database.

PostgreSQL

  1. Click “New” → “Database” → “PostgreSQL”
  2. Railway creates a database instantly
  3. Automatically sets DATABASE_URL in your app

Use in your app:

const { Pool } = require('pg');

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: {
    rejectUnauthorized: false
  }
});

app.get('/users', async (req, res) => {
  const result = await pool.query('SELECT * FROM users');
  res.json(result.rows);
});

Connect to database:

railway connect postgres

Opens a PostgreSQL shell.

Redis

  1. Click “New” → “Database” → “Redis”
  2. Railway sets REDIS_URL automatically

Use in your app:

const redis = require('redis');

const client = redis.createClient({
  url: process.env.REDIS_URL
});

await client.connect();

app.get('/cache/:key', async (req, res) => {
  const value = await client.get(req.params.key);
  res.json({ value });
});

MySQL

  1. Click “New” → “Database” → “MySQL”
  2. Get MYSQL_URL automatically

MongoDB

  1. Click “New” → “Database” → “MongoDB”
  2. Get MONGO_URL automatically

All databases work the same way—click to add, instantly connected.

Environment variables

Set variables

Dashboard:

  1. Open your service
  2. Click “Variables”
  3. Add key-value pairs

CLI:

railway variables set API_KEY=your-key
railway variables set NODE_ENV=production

Use in code

const apiKey = process.env.API_KEY;
const nodeEnv = process.env.NODE_ENV;

Railway provides variables

Railway automatically sets:

Custom domains

Add your domain:

  1. Open your service
  2. Click “Settings”
  3. Scroll to “Domains”
  4. Click “Custom Domain”
  5. Enter your domain: example.com

Railway shows DNS records:

CNAME: example.com → your-app.up.railway.app

Add this to your DNS provider.

HTTPS is automatic - Railway handles SSL certificates via Let’s Encrypt.

Environments

Create multiple environments (staging, production, etc.):

  1. Click “New Environment”
  2. Name it (e.g., “staging”)
  3. Deploy to it

Each environment has:

Deploy to specific environment:

railway up -e staging

Preview deployments

Every pull request gets its own deployment automatically.

  1. Create a PR in GitHub
  2. Railway builds and deploys it
  3. Comments on your PR with the URL
  4. Test the changes
  5. Merge when ready

Perfect for reviewing changes before production.

Build configuration

Railway auto-detects most apps, but you can customize:

Using railway.toml

Create railway.toml:

[build]
builder = "NIXPACKS"
buildCommand = "npm run build"

[deploy]
startCommand = "npm start"
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 10

Using Dockerfile

Create Dockerfile:

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

Railway uses your Dockerfile automatically.

Nixpacks (default)

Railway uses Nixpacks by default. It detects:

Usually no configuration needed.

Scaling

Vertical scaling

Increase resources for your service:

  1. Open service settings
  2. Adjust CPU and RAM limits
  3. Save

Horizontal scaling

Railway doesn’t support automatic horizontal scaling yet. For high traffic:

For most apps, vertical scaling is enough.

Monitoring and logs

View logs

Dashboard:

  1. Open your service
  2. Click “Deployments”
  3. Click latest deployment
  4. View logs

CLI:

railway logs

Follow logs:

railway logs --follow

Metrics

Dashboard shows:

Click “Metrics” in your service.

Health checks

Railway monitors your app. If it crashes, Railway restarts it automatically.

Add a health endpoint:

app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

Cron jobs and scheduled tasks

Deploy cron jobs easily:

cron.js:

const cron = require('node-cron');

// Run every hour
cron.schedule('0 * * * *', async () => {
  console.log('Running scheduled task');
  
  // Your task here
  await cleanupOldData();
  await sendDailyEmail();
});

console.log('Cron job started');

// Keep process alive
process.stdin.resume();

Deploy as a separate service. It runs continuously.

Private networking

Services in the same project can communicate privately:

Service 1 (API):

// Runs on https://api-production.up.railway.app

Service 2 (Worker):

// Can call API privately
const response = await fetch('http://api.railway.internal:3000/process');

Use .railway.internal for private communication.

Monorepo support

Railway supports monorepos:

  1. Add service
  2. Set “Root Directory” in settings
  3. Railway only builds that directory

Example structure:

my-monorepo/
├── api/
│   └── package.json
├── web/
│   └── package.json
└── worker/
    └── package.json

Create 3 services, each pointing to its directory.

Real-world example: Full-stack app

Let’s deploy a complete app with API, database, and worker.

Project structure

my-fullstack-app/
├── api/
│   ├── index.js
│   └── package.json
├── worker/
│   ├── index.js
│   └── package.json
└── README.md

API (api/index.js)

const express = require('express');
const { Pool } = require('pg');
const redis = require('redis');

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

// Database
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: { rejectUnauthorized: false }
});

// Redis
const redisClient = redis.createClient({
  url: process.env.REDIS_URL
});
redisClient.connect();

app.use(express.json());

app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.get('/api/users', async (req, res) => {
  const { rows } = await pool.query('SELECT * FROM users');
  res.json(rows);
});

app.post('/api/users', async (req, res) => {
  const { name, email } = req.body;
  
  // Insert user
  const { rows } = await pool.query(
    'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
    [name, email]
  );
  
  // Queue welcome email
  await redisClient.lPush('email-queue', JSON.stringify({
    to: email,
    userId: rows[0].id
  }));
  
  res.status(201).json(rows[0]);
});

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

Worker (worker/index.js)

const redis = require('redis');
const nodemailer = require('nodemailer');

const redisClient = redis.createClient({
  url: process.env.REDIS_URL
});

const mailer = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: 587,
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS
  }
});

async function processEmails() {
  await redisClient.connect();
  console.log('Worker started');
  
  while (true) {
    try {
      const job = await redisClient.brPop('email-queue', 5);
      
      if (job) {
        const data = JSON.parse(job.element);
        
        await mailer.sendMail({
          to: data.to,
          subject: 'Welcome!',
          text: 'Thanks for joining!'
        });
        
        console.log(`Email sent to ${data.to}`);
      }
    } catch (error) {
      console.error('Error:', error);
    }
  }
}

processEmails();

Deploy to Railway

1. Push to GitHub:

git init
git add .
git commit -m "Initial commit"
git push origin main

2. Create project on Railway:

3. Add PostgreSQL:

4. Add Redis:

5. Add API service:

6. Add Worker service:

7. Set environment variables:

8. Done!

You now have:

Integrating with GitHub Actions

Automate additional steps:

.github/workflows/deploy.yml:

name: Deploy to Railway

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Install Railway CLI
        run: npm i -g @railway/cli
      
      - name: Deploy
        run: railway up -s api
        env:
          RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}

Get Railway token:

railway login
railway whoami --token

Add to GitHub secrets.

Pricing

Railway has simple, fair pricing:

Free trial:

Hobby plan ($5/month):

Pro plan ($20/month):

Usage costs:

Example costs:

Much cheaper than Heroku, similar to Fly.io.

CLI commands

Project:

railway init              # Initialize project
railway link              # Link to existing project
railway status            # Project status

Deploy:

railway up                # Deploy
railway up -e staging     # Deploy to environment
railway up -d             # Detached mode

Logs:

railway logs              # View logs
railway logs -f           # Follow logs
railway logs -n 100       # Last 100 lines

Variables:

railway variables         # List variables
railway variables set KEY=value
railway variables unset KEY

Database:

railway connect postgres  # Connect to PostgreSQL
railway connect redis     # Connect to Redis
railway connect mysql     # Connect to MySQL

Services:

railway service           # List services
railway open              # Open dashboard

Other:

railway whoami            # Show account info
railway logout            # Logout
railway help              # Help

Best practices

Always use environment variables for secrets. Never hardcode API keys or passwords in your code. Railway makes this easy with built-in secrets management.

Add a health check endpoint to your app. A simple /health route that returns { status: 'ok' } helps Railway monitor your app and restart it if needed.

Use separate environments for production and staging. They should be completely isolated with different databases and environment variables. This prevents staging bugs from affecting production.

Enable preview deployments for every PR. Being able to test changes in a real environment before merging is invaluable. I catch so many issues this way.

Monitor your metrics regularly. Check CPU and memory usage in the dashboard. If you see spikes, investigate why. Better to optimize early than get a surprise bill.

Set up custom domains for professional apps. The Railway subdomain is fine for testing, but production apps should use your own domain.

Keep your databases in the same Railway project as your app. This gives you lower latency and automatic connection setup. No need to manage connection strings across services.

Keep services small and focused. One service should have one responsibility. This makes them easier to debug, scale, and maintain.

Use railway.toml when you need to override defaults. Most of the time you won’t need it, but it’s there when the automatic detection doesn’t match your setup.

Review your usage regularly in the dashboard. Railway’s pricing is transparent, but it’s good to keep an eye on costs to avoid surprises.

Common use cases

Static site with API

Frontend on Vercel, API on Railway:

  1. Deploy Next.js/React to Vercel
  2. Deploy API to Railway
  3. Set NEXT_PUBLIC_API_URL in Vercel to Railway URL
  4. CORS configured on Railway

Full-stack monorepo

Structure:

app/
├── frontend/ (Next.js)
├── backend/ (Express)
└── worker/

Railway setup:

Microservices

Deploy multiple services:

All in one Railway project, connected via private network.

Scheduled jobs

Deploy cron jobs:

Keep them running 24/7.

Troubleshooting

Build fails: Check logs in deployment details. Common issues:

App crashes: View logs to find the error:

railway logs -f

Database connection fails: Ensure DATABASE_URL is set:

railway variables

Out of memory: Increase memory limit in service settings.

Slow performance:

Custom domain not working:

Railway vs alternatives

vs Heroku:

vs Vercel:

vs Fly.io:

vs Render:

My take: Railway is the best for rapid development and deployment. If you need complex infrastructure, use Fly.io or AWS.

When to use Railway

Railway is perfect for side projects where you want to deploy fast without thinking about infrastructure. It’s also great for MVPs and prototypes when you need to ship quickly.

I use it for small to medium production apps, APIs and backends, full-stack applications, microservices, and scheduled jobs. It handles all of these really well.

For static sites, I’d use Vercel instead. Railway can host them, but Vercel is optimized for that use case. And for enterprise apps with complex requirements or specific infrastructure needs, you might need AWS or GCP.

If you’re expecting massive scale with millions of requests per day, you’ll probably outgrow Railway eventually. But honestly, most apps never reach that scale.

Migrating from Heroku

Railway makes it easy:

  1. Export Heroku database:
heroku pg:backups:capture
heroku pg:backups:download
  1. Create Railway PostgreSQL database

  2. Import data:

railway connect postgres
\i latest.dump
  1. Deploy app to Railway (same process as new app)

  2. Update DNS to point to Railway

  3. Done!

Tips and tricks

1. Use Railway’s templates Start with templates for common stacks:

2. Connect local to Railway database

railway connect postgres

Develop locally with production data (be careful!).

3. Use Railway CLI for quick tests

railway run node script.js

Runs scripts with Railway environment variables.

4. Share projects with team Invite collaborators in project settings.

5. Set up notifications Get Discord/Slack alerts for deployments and errors.

Conclusion

Railway is my favorite deployment platform for backend apps. It’s fast, simple, and developer-friendly. The zero-config approach means I spend less time on DevOps and more time building features.

I use Railway for:

It’s not perfect for everything—Vercel is better for static sites, Fly.io for global edge deployments. But for traditional web apps and APIs, Railway is hard to beat.

The free trial gives you $5 of credit. That’s enough to run several apps for weeks. Try it. Deploy something. See how easy it is.

Most developers who try Railway stick with it. The DX is that good.

Go to railway.app and get started. Deploy your first app in 5 minutes.

You’ll see why I love it.


I wrote 20 books to help you become a better developer:

  • JavaScript Handbook
  • TypeScript Handbook
  • CSS Handbook
  • Node.js Handbook
  • Astro Handbook
  • HTML Handbook
  • Next.js Pages Router Handbook
  • Alpine.js Handbook
  • HTMX Handbook
  • React Handbook
  • SQL Handbook
  • Git Cheat Sheet
  • Laravel Handbook
  • Express Handbook
  • Swift Handbook
  • Go Handbook
  • PHP Handbook
  • Python Handbook
  • Linux/Mac CLI Commands Handbook
  • C Handbook
...download them all now!

Related posts about deployment: