Deployment — Railway + Vercel
Stack principal: Railway para Node.js/Express/SQLite · Vercel para Next.js
1. Decision Tree
¿Es un Next.js frontend/fullstack?
├── SÍ → Vercel (deploy automático desde GitHub)
└── NO (Node.js / Express / backend / worker)
├── ¿Necesita volumen persistente (SQLite)? → Railway
├── ¿Stateless con DB externa? → Railway o Fly.io
└── ¿Docker container específico? → Railway (soporta Dockerfile)
2. Vercel — Next.js
Setup inicial
npm install -g vercel
vercel login
vercel --prod # deploy manual
# O conectar repo GitHub en vercel.com (recomendado)
# Push a main → auto-deploy
vercel.json (opcional)
{
"buildCommand": "prisma generate && next build",
"installCommand": "npm ci",
"framework": "nextjs",
"regions": ["iad1"]
}
Variables de entorno
# CLI
vercel env add DATABASE_URL production
vercel env add AUTH_SECRET production
# O en vercel.com → Project → Settings → Environment Variables
Build command con Prisma
// package.json
{
"scripts": {
"postinstall": "prisma generate",
"build": "next build"
}
}
Vercel + Neon (PostgreSQL serverless)
# Connection pooling para serverless
DATABASE_URL=postgresql://user:pass@ep-xxx.neon.tech/db?pgbouncer=true&connection_limit=1
DIRECT_URL=postgresql://user:pass@ep-xxx.neon.tech/db # para migraciones
3. Railway — Node.js / Express
Setup inicial
npm install -g @railway/cli
railway login
railway init # crear proyecto nuevo
railway up # deploy manual
railway.toml (configuración del proyecto)
[build]
builder = "NIXPACKS"
[deploy]
startCommand = "node src/index.js"
healthcheckPath = "/health"
healthcheckTimeout = 30
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 3
Variables de entorno en Railway
# CLI
railway variables set NODE_ENV=production
railway variables set JWT_SECRET=your-secret
railway variables set PORT=3000
# O en railway.app → Project → Variables
Variables que Railway inyecta automáticamente:
PORT— puerto asignado (siempre usarprocess.env.PORT)RAILWAY_ENVIRONMENT—production/stagingRAILWAY_STATIC_URL— URL pública del servicio
Health check endpoint (requerido)
// src/index.js
app.get('/health', (req, res) => {
res.json({ ok: true, env: process.env.NODE_ENV, ts: Date.now() })
})
4. Dockerfile (Railway — control total)
FROM node:20-alpine
WORKDIR /app
# Instalar dependencias primero (cache layer)
COPY package*.json ./
RUN npm ci --only=production
# Copiar código
COPY . .
# Puerto
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "src/index.js"]
Para SQLite con volumen persistente en Railway
# Crear directorio de datos
RUN mkdir -p /app/data
VOLUME ["/app/data"]
CMD ["node", "src/index.js"]
# En Railway: Settings → Volumes → Mount path: /app/data
5. Variables de entorno — Buenas prácticas
.env.example (siempre en repo, sin valores reales)
# Server
PORT=3000
NODE_ENV=development
# Database
DATABASE_URL=./data/db.sqlite
# Para PostgreSQL: postgresql://user:pass@host:5432/db
# Auth
JWT_SECRET= # min 32 chars — generar con: openssl rand -hex 32
JWT_REFRESH_SECRET= # diferente al anterior
# WhatsApp / Baileys
WA_SESSION_DIR=./data/wa-sessions
# GHL OAuth
GHL_CLIENT_ID=
GHL_CLIENT_SECRET=
GHL_REDIRECT_URI=https://yourapp.railway.app/oauth/callback
# Resend
RESEND_API_KEY=
Validar env vars al arranque
// src/lib/env.js
const required = ['JWT_SECRET', 'DATABASE_URL', 'GHL_CLIENT_ID']
for (const key of required) {
if (!process.env[key]) {
console.error(`❌ Missing required env var: ${key}`)
process.exit(1)
}
}
console.log('✅ Environment validated')
6. Prisma en producción
Build command Railway
npx prisma migrate deploy && node src/index.js
O en railway.toml
[build]
buildCommand = "npm ci && npx prisma generate"
[deploy]
startCommand = "npx prisma migrate deploy && node src/index.js"
7. Dominios custom
Vercel
vercel domains add myapp.com
# Agregar CNAME en DNS: @ → cname.vercel-dns.com
Railway
# En railway.app → Settings → Networking → Custom Domain
# Agregar CNAME: app → yourapp.railway.app
8. CI/CD con GitHub Actions
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm test
- name: Deploy to Railway
uses: bervproject/railway-deploy@v1
with:
railway-token: ${{ secrets.RAILWAY_TOKEN }}
service: your-service-name
9. Monitoreo post-deploy
# Railway logs en tiempo real
railway logs --tail
# Vercel logs
vercel logs your-deployment-url
# Estado del servicio
railway status
Checklist post-deploy
- Health check responde (
/health→{ ok: true }) - Variables de entorno configuradas
- Migraciones de DB aplicadas
- Dominio custom apunta correctamente
- HTTPS activo (Railway y Vercel lo hacen automático)
- Logs sin errores en los primeros 5 minutos
10. Rollback
# Vercel — redeployar versión anterior desde dashboard
# o CLI:
vercel rollback
# Railway — desde railway.app → Deployments → redeploy commit anterior
Costos de referencia (2026)
| Plataforma | Plan | Costo | Límites |
|---|---|---|---|
| Vercel | Hobby | $0 | 100GB bandwidth, funciones serverless |
| Vercel | Pro | $20/mes | Más bandwidth, team collaboration |
| Railway | Starter | $5 crédito gratis | ~$0.000463/vCPU/min |
| Railway | Pro | $20/mes | Sin límite de proyectos, más recursos |