Complete step-by-step guide to deploy TaskMan to Railway.
- Railway account (sign up at https://railway.app)
- Railway CLI installed (
npm install -g @railway/clior download from https://docs.railway.app/develop/cli) - Project pushed to a Git repository (GitHub, GitLab, or Bitbucket)
Railway will run 3 services:
- PostgreSQL Database - Managed PostgreSQL instance
- Backend API - Express + Prisma backend
- Frontend - React + Vite frontend
- Go to https://railway.app/new
- Click "New Project"
- Click "Create Empty Project"
- Give it a name (e.g., "TaskMan")
Then set up the CLI on your local machine:
# Login to Railway
railway login
# Link this directory to your Railway project
railway link
# Verify the link
railway statusYou should see your project listed. You now have an empty Railway project ready to add services.
- Go to your Railway project dashboard
- Click "New Service" (or "+ Add Service")
- Select "Database" → "PostgreSQL"
- Railway will provision a PostgreSQL instance and automatically set the
DATABASE_URLvariable - The database URL will be automatically available to any service that needs it
- In Railway dashboard, click "New Service" → "GitHub Repo"
- Connect your GitHub account if not already connected
- Select the TaskMan repository
- Set Root Directory:
backend - Railway will auto-detect the
Dockerfileand deploy
After backend deploys:
- Go to Backend service → "Settings" tab
- Look for "Networking" section
- Click "Generate Domain"
- Copy the generated URL (e.g.,
https://taskman-backend-production.up.railway.app) - Save this URL - you'll need it for the frontend in Step 4
In the Backend service, go to "Variables" tab and add:
| Variable | Value | How to Get |
|---|---|---|
DATABASE_URL |
(auto-set) | Already injected from PostgreSQL service |
JWT_SECRET |
Generate strong secret | Run: openssl rand -base64 32 |
JWT_EXPIRES_IN |
7d |
Fixed value |
CORS_ORIGIN |
Frontend URL | You'll add this in Step 5 (after frontend deploys) |
NODE_ENV |
production |
Fixed value |
PORT |
4000 |
Fixed value |
To generate JWT_SECRET:
# On Mac/Linux
openssl rand -base64 32
# On Windows (PowerShell)
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))CORS_ORIGIN empty for now. You'll update it after the frontend is deployed (Step 5).
- In Railway dashboard, click "New Service" → "GitHub Repo"
- Select the TaskMan repository
- Set Root Directory:
frontend - Railway will auto-detect the
Dockerfileand deploy
In the Frontend service, go to "Variables" tab and add:
| Variable | Value |
|---|---|
VITE_API_URL |
Paste the backend URL from Step 3.2 |
Example: If your backend URL is https://taskman-backend-production.up.railway.app, then:
VITE_API_URL=https://taskman-backend-production.up.railway.app
After adding this variable, the frontend will automatically redeploy with the correct backend URL.
After frontend deploys:
- Go to Frontend service → "Settings" tab
- Look for "Networking" section
- Click "Generate Domain"
- Copy the generated URL (e.g.,
https://taskman-frontend-production.up.railway.app) - Save this URL - you'll need it for the backend in Step 5
Now that both services have their URLs, connect them:
- Go to Backend service → "Variables" tab
- Find the
CORS_ORIGINvariable (you left it empty) - Paste the frontend URL from Step 4.3
- Example: If your frontend URL is
https://taskman-frontend-production.up.railway.app, then:CORS_ORIGIN=https://taskman-frontend-production.up.railway.app - Click save - the backend will automatically redeploy with this setting
After the backend redeploys:
- Frontend should now be able to communicate with the backend
- Cross-origin requests should work properly
Railway automatically deploys whenever you push to your main branch.
If you need to manually trigger a deployment:
# Deploy backend
railway up --service backend
# Deploy frontend
railway up --service frontend
# Deploy both
railway upThe backend Dockerfile automatically runs prisma migrate deploy on startup, so your database schema will be set up automatically when the backend first deploys.
If needed, you can manually run migrations:
# Manually run migrations
railway run --service backend npx prisma migrate deploy
# Seed database (optional - creates test data)
railway run --service backend npx prisma db seedAfter deployment, verify these endpoints:
curl https://your-backend-url.railway.app/health
# Should return: {"status":"ok","timestamp":"..."}Open https://your-frontend-url.railway.app in browser:
- Frontend loads without errors
- Can navigate to /login
- Can register a new user
- After registration, redirected to dashboard
- Can create a project
- Can create a task
- Can logout and login again
- Session persists after page refresh
PORT=4000
DATABASE_URL=postgresql://user:pass@host:5432/dbname?schema=public
JWT_SECRET=your-super-secret-key-here
JWT_EXPIRES_IN=7d
CORS_ORIGIN=https://your-frontend-url.railway.app
NODE_ENV=productionVITE_API_URL=https://your-backend-url.railway.appIf you're using Railway CLI for automation (CI/CD, scripts, etc.), use project tokens:
- Go to Project Settings → Tokens
- Click "Create Token"
- Select your environment (production)
- Copy the token (starts with
rw_production_...)
# Set token as environment variable
export RAILWAY_TOKEN='your-token-here'
# Check status
railway status
# Deploy backend
railway up --service backend
# Deploy frontend
railway up --service frontend
# Run migrations
railway run --service backend npx prisma migrate deployFor GitHub Actions or other CI/CD:
- name: Deploy to Railway
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: |
railway up --service backend
railway up --service frontend- Check logs:
railway logs --service backend - Verify DATABASE_URL is set correctly
- Ensure JWT_SECRET is set
- Check CORS_ORIGIN matches frontend URL
- Verify VITE_API_URL is correct
- Check CORS_ORIGIN on backend includes frontend URL
- Both should use HTTPS in production
- Verify PostgreSQL service is running
- Check DATABASE_URL format in backend service
- Ensure migrations ran successfully
- CORS_ORIGIN must match frontend URL exactly
- Both services should be on Railway domains (for cookie security)
- JWT_SECRET must be set and consistent
- Check Railway build logs
- Verify Dockerfile syntax
- Ensure package-lock.json is committed to repo
If a deployment breaks:
- Go to Service → Deployments
- Find the last working deployment
- Click "Redeploy"
Or via CLI:
railway rollback --service backend
railway rollback --service frontend# Backend logs
railway logs --service backend
# Frontend logs
railway logs --service frontend
# Database logs
railway logs --service postgresRailway provides metrics in the dashboard:
- CPU usage
- Memory usage
- Network traffic
- Request count
Railway pricing is based on resource usage:
- Development: Use Railway's free trial ($5 credit)
- Production: Monitor usage in Settings → Usage
- Optimization tips:
- Scale down services during off-hours if needed
- Use Railway's sleep feature for non-critical environments
- Monitor database size and clean up test data
To use your own domain:
- Go to Service → Settings → Networking
- Click "Add Custom Domain"
- Enter your domain (e.g.,
taskman.yourdomain.com) - Add the CNAME record to your DNS provider
- Railway will auto-provision SSL certificates
- Set up monitoring and alerts
- Configure backup strategy for PostgreSQL
- Set up staging environment (duplicate project)
- Configure CD pipeline for automatic deployments
- Add error tracking (Sentry, LogRocket, etc.)
- Enable Railway's built-in metrics and observability
- Railway Docs: https://docs.railway.app
- Railway Discord: https://discord.gg/railway
- Railway Status: https://status.railway.app
- Never commit secrets to your repository
- Use Railway's secret management for sensitive values
- Rotate JWT_SECRET periodically
- Enable Railway's DDoS protection
- Review CORS settings regularly
- Keep dependencies updated