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
- Go to railway.app
- Click “New Project”
- Choose “Deploy from GitHub repo”
- Select your repository
- Click “Deploy”
Option 2: CLI
railway init
railway up
That’s it. Railway:
- Detects Node.js
- Runs
npm install
- Runs
npm start
- Deploys your app
- Gives you a URL:
https://my-app-production.up.railway.app
No configuration needed.
Understanding Railway projects
A Railway project contains:
- Services (your apps)
- Databases (Postgres, MySQL, Redis, etc.)
- Environments (production, staging, etc.)
- Variables (environment variables)
Each service gets:
- Automatic HTTPS
- Unique URL
- Environment isolation
- Resource limits
Adding a database
Click “New” → “Database” → Choose your database.
PostgreSQL
- Click “New” → “Database” → “PostgreSQL”
- Railway creates a database instantly
- 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
- Click “New” → “Database” → “Redis”
- 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
- Click “New” → “Database” → “MySQL”
- Get
MYSQL_URL
automatically
MongoDB
- Click “New” → “Database” → “MongoDB”
- Get
MONGO_URL
automatically
All databases work the same way—click to add, instantly connected.
Environment variables
Set variables
Dashboard:
- Open your service
- Click “Variables”
- 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:
PORT
- Port to listen onRAILWAY_ENVIRONMENT
- Environment nameRAILWAY_PROJECT_ID
- Project IDRAILWAY_SERVICE_ID
- Service ID- Database URLs when you add databases
Custom domains
Add your domain:
- Open your service
- Click “Settings”
- Scroll to “Domains”
- Click “Custom Domain”
- 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.):
- Click “New Environment”
- Name it (e.g., “staging”)
- Deploy to it
Each environment has:
- Separate deployments
- Separate databases
- Separate variables
- Separate URLs
Deploy to specific environment:
railway up -e staging
Preview deployments
Every pull request gets its own deployment automatically.
- Create a PR in GitHub
- Railway builds and deploys it
- Comments on your PR with the URL
- Test the changes
- 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:
- Node.js (npm, yarn, pnpm)
- Python (pip, poetry)
- Go
- Ruby (bundler)
- PHP
- Java
- Rust
- And more
Usually no configuration needed.
Scaling
Vertical scaling
Increase resources for your service:
- Open service settings
- Adjust CPU and RAM limits
- Save
Horizontal scaling
Railway doesn’t support automatic horizontal scaling yet. For high traffic:
- Use a load balancer
- Deploy multiple services manually
- Or use Railway’s edge proxy (coming soon)
For most apps, vertical scaling is enough.
Monitoring and logs
View logs
Dashboard:
- Open your service
- Click “Deployments”
- Click latest deployment
- View logs
CLI:
railway logs
Follow logs:
railway logs --follow
Metrics
Dashboard shows:
- CPU usage
- Memory usage
- Network traffic
- Request count
- Response times
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:
- Add service
- Set “Root Directory” in settings
- 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:
- New Project → Deploy from GitHub
- Select repository
3. Add PostgreSQL:
- New → Database → PostgreSQL
4. Add Redis:
- New → Database → Redis
5. Add API service:
- New → GitHub Repo
- Set Root Directory:
api
6. Add Worker service:
- New → GitHub Repo
- Set Root Directory:
worker
7. Set environment variables:
- Open worker service
- Add
SMTP_HOST
,SMTP_USER
,SMTP_PASS
8. Done!
You now have:
- API at
https://api-production.up.railway.app
- Worker processing emails in background
- PostgreSQL database
- Redis queue
- Everything connected automatically
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:
- $5 of usage credit
- No credit card required
- Perfect for testing
Hobby plan ($5/month):
- $5 usage included
- Additional usage charged
- Unlimited projects
- Community support
Pro plan ($20/month):
- $20 usage included
- Priority support
- Team collaboration
- Advanced features
Usage costs:
- Compute: ~$0.000463/GB-hour
- Memory: ~$0.000231/GB-hour
- Storage: ~$0.25/GB/month
- Network: $0.10/GB
Example costs:
- Small API: ~$2-5/month
- Medium app: ~$10-15/month
- Large app: ~$30-50/month
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:
- Deploy Next.js/React to Vercel
- Deploy API to Railway
- Set
NEXT_PUBLIC_API_URL
in Vercel to Railway URL - CORS configured on Railway
Full-stack monorepo
Structure:
app/
├── frontend/ (Next.js)
├── backend/ (Express)
└── worker/
Railway setup:
- 3 services, each pointing to its directory
- Share database across all services
- Use private networking between services
Microservices
Deploy multiple services:
- User service
- Auth service
- Payment service
- Notification service
All in one Railway project, connected via private network.
Scheduled jobs
Deploy cron jobs:
- Daily backups
- Cleanup tasks
- Report generation
- Data synchronization
Keep them running 24/7.
Troubleshooting
Build fails: Check logs in deployment details. Common issues:
- Missing dependencies
- Wrong Node version (set in
package.json
) - Build command failed
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:
- Check metrics
- Optimize queries
- Increase CPU/memory
- Add caching (Redis)
Custom domain not working:
- Verify DNS records
- Wait for propagation (up to 24 hours)
- Check SSL certificate status
Railway vs alternatives
vs Heroku:
- Railway is cheaper
- Better DX (dashboard, CLI)
- Faster deploys
- More modern
vs Vercel:
- Vercel for frontend/static
- Railway for backend/databases
- Use both together
vs Fly.io:
- Railway is simpler (no Docker required)
- Fly.io more flexible (full VMs)
- Railway better DX
- Fly.io better for global edge
vs Render:
- Similar features
- Railway has better UI
- Render has free tier for static sites
- Both are good choices
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:
- Export Heroku database:
heroku pg:backups:capture
heroku pg:backups:download
-
Create Railway PostgreSQL database
-
Import data:
railway connect postgres
\i latest.dump
-
Deploy app to Railway (same process as new app)
-
Update DNS to point to Railway
-
Done!
Tips and tricks
1. Use Railway’s templates Start with templates for common stacks:
- Next.js
- Express + PostgreSQL
- Django
- Rails
- And more
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:
- Client projects (APIs, full-stack apps)
- Side projects
- Prototypes and MVPs
- Scheduled jobs
- Microservices
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