Módulo 7: Alternative Platforms (Render, Railway, Fly.io)
3. Railway: Deployment para AI Apps
Descripción
En esta cápsula vas a desplegar tu app AI en Railway. Si Render es "Heroku moderno," Railway es "Heroku reimaginado para developers." Railway se diferencia por su developer experience excepcional: deploy desde CLI en una línea, previews automáticos por PR, add-ons integrados (PostgreSQL, Redis, MySQL, MongoDB) con un click, y un modelo de pricing basado en uso real (no en plan fijo). Es la plataforma que muchos developers eligen para prototipos que necesitan estar online rápido.
Contexto: Vienes de desplegar en Render (cápsula 02). La misma app, la misma meta — pero diferente plataforma, diferente experiencia. La comparación directa te da datos reales para tu decision matrix. Railway hace algunas cosas mejor que Render (CLI, add-ons, previews) y otras peor (pricing menos predecible, menos opciones de plan fijo). Al terminar tendrás la experiencia de primera mano para evaluar.
Railway: Overview de la Plataforma
Qué es Railway
Railway es una plataforma cloud que despliega aplicaciones desde Git o desde CLI. Su propuesta de valor es velocidad y simplicidad:
- Web Services: Apps con servidor (cualquier lenguaje/framework)
- Add-ons integrados: PostgreSQL, Redis, MySQL, MongoDB — un click para agregar
- Preview Environments: Cada Pull Request genera un deploy temporal
- CLI potente:
railway updesde tu terminal despliega en segundos - Templates: Apps pre-configuradas (Next.js + Prisma, FastAPI + PostgreSQL, etc.)
Modelo de deployment
Opción A: Desde Git (automático)
Tu código (GitHub) → push a main → Railway detecta → Build → Deploy → URL pública
Opción B: Desde CLI (manual)
Terminal → railway up → Railway empaqueta y sube → Build → Deploy → URL pública
Opción C: Desde template
Railway dashboard → Template → Click → Deploy → URL pública
Railway detecta automáticamente tu stack:
Dockerfile presente → Docker build
requirements.txt → Python buildpack (Nixpacks)
package.json → Node buildpack (Nixpacks)
go.mod → Go buildpack
Cargo.toml → Rust buildpack
Pricing (datos actualizados 2026)
Railway usa un modelo de pricing basado en uso, no en planes fijos de recursos:
| Plan | Precio base | Créditos incluidos | Uso |
|---|---|---|---|
| Trial | $0 | $5 de crédito | 500 horas de ejecución, sin custom domains |
| Hobby | $5/mes | $5 de crédito (total $10) | Custom domains, deploy desde Git |
| Pro | $20/mes per seat | $10 de crédito (total $30) | Teams, SLA, soporte prioritario |
Costes de uso (después de créditos):
| Recurso | Precio |
|---|---|
| vCPU | $0.000463/min ($0.02778/hr) |
| RAM (GB) | $0.000231/min ($0.01388/hr) |
| Disco (GB) | $0.000308/min ($0.01852/hr) |
| Egress | $0.10/GB después de 100 GB/mes incluidos |
Estimación para AI app típica (1 vCPU, 1 GB RAM, 24/7):
- vCPU: $0.02778 × 730 hrs = ~$20/mes
- RAM: $0.01388 × 730 hrs = ~$10/mes
- Total infra: ~$30/mes (Hobby: ~$25 después de créditos)
Para AI workloads:
- Trial: Solo para pruebas rápidas. $5 de crédito se consume en ~1 semana con una app corriendo 24/7.
- Hobby: Viable para MVPs y proyectos personales. ~$20-30/mes para una app AI básica.
- Pro: Para equipos. El mismo pricing por uso, pero con SLA y features de equipo.
Limitaciones para AI
| Limitación | Impacto en AI | Workaround |
|---|---|---|
| Trial: 500 horas | ~21 días de ejecución continua | Upgrade a Hobby ($5/mes) |
| Max 32 GB RAM (Pro) | Suficiente para la mayoría, no para modelos XL | Usar APIs externas para modelos grandes |
| Max 32 vCPU (Pro) | Suficiente para AI con APIs externas | No correr inferencia local de modelos pesados |
| Request timeout: 5 min | Mejor que Render (30s), pero aún limitante | Streaming para respuestas muy largas |
| No GPU | No puedes correr CUDA/modelos locales | APIs de inferencia (OpenAI, Together AI) |
| Disco efímero por defecto | Datos se pierden en cada deploy | Usar volumes persistentes (add-on) |
| Egress: 100 GB/mes incluidos | Suficiente para la mayoría de apps AI | Monitorear si sirves archivos grandes |
Deploy Step-by-Step: App AI en Railway
Paso 1: Preparar y verificar localmente
cd deployment-cloud-guide/module-07/app
# Verificar que la app funciona
docker build -t docusearch-ai .
docker run -p 8000:8000 -e OPENAI_API_KEY=sk-test docusearch-ai
curl http://localhost:8000/health
Paso 2: Deploy desde CLI (la forma más rápida)
# Login (abre browser para autenticación)
railway login
# Crear nuevo proyecto
railway init
# ? Project name: docusearch-ai
# ✅ Project created: docusearch-ai
# Vincular al directorio actual
railway link
# Configurar variables de entorno
railway variables set OPENAI_API_KEY=sk-proj-xxx
railway variables set PLATFORM=railway
railway variables set LOG_LEVEL=info
# Deploy
railway up
# ✅ Uploading... done
# ✅ Building... done
# ✅ Deploying... done
# 🎉 https://docusearch-ai-production.up.railway.app
# Verificar
curl https://docusearch-ai-production.up.railway.app/health
Eso es todo. Tres comandos: railway init, railway variables set, railway up. Tu app está online.
Paso 3: Deploy desde GitHub (automático)
# En Railway Dashboard:
# 1. New Project → Deploy from GitHub Repo
# 2. Seleccionar repositorio
# 3. Railway detecta Dockerfile automáticamente
# 4. Click "Deploy Now"
# Configurar variables:
# Dashboard → tu servicio → Variables → Raw Editor
# OPENAI_API_KEY=sk-proj-xxx
# PLATFORM=railway
A partir de ahora, cada push a main genera un deploy automático.
Paso 4: Configurar railway.toml
Railway permite configuración declarativa con railway.toml:
# railway.toml
[build]
dockerfilePath = "Dockerfile"
[deploy]
startCommand = "uvicorn main:app --host 0.0.0.0 --port $PORT"
healthcheckPath = "/health"
healthcheckTimeout = 30
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 3
git add railway.toml
git commit -m "Add Railway configuration"
git push origin main
# Railway detecta el cambio y redespliega
Paso 5: Generar dominio público
# Desde CLI
railway domain
# ✅ Domain: docusearch-ai-production.up.railway.app
# Custom domain (requiere plan Hobby+)
# Dashboard → tu servicio → Settings → Domains
# Add custom domain: api.tu-dominio.com
# Railway te da un CNAME para configurar en tu DNS
Paso 6: Verificar deployment completo
RAILWAY_URL="https://docusearch-ai-production.up.railway.app"
# Health check
curl $RAILWAY_URL/health
# Inferencia
curl -X POST $RAILWAY_URL/ask \
-H "Content-Type: application/json" \
-d '{"question": "¿Qué ventajas tiene Railway?", "max_tokens": 200}'
# Logs
railway logs
# 2026-03-08T15:30:00Z INFO Application startup complete
# 2026-03-08T15:30:05Z INFO POST /ask 200 1.2s
Railway: Add-ons y Servicios
PostgreSQL en Railway
# Desde CLI
railway add
# ? Select a plugin: PostgreSQL
# ✅ PostgreSQL added to project
# Railway inyecta automáticamente:
# DATABASE_URL=postgresql://user:pass@host:port/dbname
# PGDATABASE, PGHOST, PGPASSWORD, PGPORT, PGUSER
Desde el dashboard:
Dashboard → tu proyecto → New → Database → PostgreSQL
Railway crea la instancia y conecta las variables automáticamente.
import os
DATABASE_URL = os.environ.get("DATABASE_URL")
# Railway inyecta esta variable automáticamente
# No necesitas copiar/pegar la URL manualmente
Redis en Railway
railway add
# ? Select a plugin: Redis
# ✅ Redis added to project
# Railway inyecta:
# REDIS_URL=redis://default:pass@host:port
# REDISHOST, REDISPASSWORD, REDISPORT, REDISUSER
import os
import redis
REDIS_URL = os.environ.get("REDIS_URL")
cache = redis.from_url(REDIS_URL)
Múltiples servicios en un proyecto
Railway permite múltiples servicios (monorepo):
Railway Project: docusearch-ai
├── Service: api (FastAPI)
│ └── Linked to: PostgreSQL, Redis
├── Service: worker (background processing)
│ └── Linked to: PostgreSQL, Redis
├── PostgreSQL instance
└── Redis instance
# Cada servicio ve las variables de los servicios vinculados
# La comunicación interna usa la red privada de Railway
# No expones PostgreSQL a internet — solo los servicios del proyecto acceden
Railway: Preview Environments
Una de las features más útiles de Railway para equipos: cada Pull Request genera un deployment temporal con su propia URL y sus propias instancias de base de datos.
main branch → Production deployment
https://docusearch-ai-production.up.railway.app
PR #42 → Preview deployment (automático)
https://docusearch-ai-pr-42.up.railway.app
└── PostgreSQL copia temporal
└── Redis copia temporal
└── Variables heredadas + overrides
Configurar Preview Environments
Dashboard → tu proyecto → Settings → Environments
- Enable PR Deploys: ON
- Auto-deploy PR: ON
Cada PR ahora genera un deploy aislado. Cuando el PR se cierra (merge o close), Railway destruye el preview automáticamente.
Variables por entorno
# Variables de producción
railway variables set OPENAI_API_KEY=sk-prod-xxx -e production
# Variables de preview (staging)
railway variables set OPENAI_API_KEY=sk-staging-xxx -e staging
Railway CLI: Comandos Esenciales
# Proyecto
railway init # Crear proyecto nuevo
railway link # Vincular directorio a proyecto existente
railway status # Ver estado del proyecto
# Deploy
railway up # Deploy desde directorio actual
railway up --detach # Deploy sin esperar
# Variables
railway variables # Listar todas las variables
railway variables set KEY=val # Agregar/actualizar variable
railway variables delete KEY # Eliminar variable
# Servicios
railway add # Agregar add-on (PostgreSQL, Redis, etc.)
# Logs y debug
railway logs # Ver logs en tiempo real
railway logs --num 100 # Últimas 100 líneas
# Dominio
railway domain # Generar dominio público
# Entornos
railway environment # Listar entornos
railway environment production # Cambiar a producción
# Conectar a base de datos
railway connect postgres # Abrir psql conectado a tu PostgreSQL
railway connect redis # Abrir redis-cli conectado a tu Redis
# Ejecutar comando remoto
railway run python manage.py migrate # Ejecutar en el contexto del servicio
Patterns de Deployment en Railway para AI
Pattern 1: FastAPI + PostgreSQL + Redis
# Setup completo en 5 comandos
railway init
railway add # → PostgreSQL
railway add # → Redis
railway variables set OPENAI_API_KEY=sk-xxx
railway up
# main.py — Railway inyecta DATABASE_URL y REDIS_URL automáticamente
import os
DATABASE_URL = os.environ["DATABASE_URL"]
REDIS_URL = os.environ["REDIS_URL"]
Pattern 2: Monorepo con API + Worker
# railway.toml para el servicio API
[build]
dockerfilePath = "Dockerfile.api"
[deploy]
startCommand = "uvicorn api.main:app --host 0.0.0.0 --port $PORT"
healthcheckPath = "/health"
# railway.toml para el worker (en otro servicio del mismo proyecto)
[build]
dockerfilePath = "Dockerfile.worker"
[deploy]
startCommand = "python worker/main.py"
Pattern 3: Scheduled jobs para AI
Railway soporta cron jobs para tareas periódicas:
Dashboard → New Service → Cron Job
Schedule: 0 2 * * * (cada día a las 2 AM)
Command: python scripts/regenerate_embeddings.py
Troubleshooting
Problema 1: "railway up falla — no detecta el proyecto"
Solución: Necesitas vincular el directorio a un proyecto Railway primero:
# Si no has creado proyecto
railway init
railway up
# Si el proyecto existe pero no está vinculado
railway link
# Selecciona tu proyecto de la lista
railway up
Problema 2: "Build falla — Nixpacks no detecta Python"
Solución: Railway usa Nixpacks por defecto. Si tienes Dockerfile, asegúrate de que esté en la raíz o especificado en railway.toml:
# railway.toml — forzar uso de Dockerfile
[build]
dockerfilePath = "Dockerfile"
Si no quieres usar Docker, asegúrate de que requirements.txt esté en la raíz:
ls
# main.py requirements.txt railway.toml
Problema 3: "App funciona pero no tiene URL pública"
Solución: Railway no genera dominio automáticamente. Necesitas generarlo:
railway domain
# ✅ https://tu-app-production.up.railway.app
O desde el dashboard: Service → Settings → Networking → Generate Domain.
Problema 4: "Variables de entorno no se aplican"
Solución: Verifica que las variables están en el entorno correcto (production vs staging):
# Ver variables actuales
railway variables
# Verificar entorno activo
railway environment
# Cambiar a production
railway environment production
railway variables
Problema 5: "El coste se disparó — no entiendo la factura"
Solución: Railway cobra por uso (CPU × tiempo + RAM × tiempo). Una app idle con 1 vCPU y 1 GB RAM consume ~$30/mes. Opciones:
# Ver uso actual
# Dashboard → tu servicio → Metrics
# Reducir recursos
# Dashboard → tu servicio → Settings → Resource Limits
# Max vCPU: 0.5
# Max RAM: 512 MB
Para apps que no necesitan estar 24/7, configura auto-sleep:
Dashboard → Service → Settings → Sleep after inactivity
Ejercicios Prácticos
Ejercicio 1: Deploy desde CLI en Railway
Despliega la misma app AI que usaste en Render, pero usando Railway CLI. Configura las variables y verifica que funciona.
Ver solución
cd deployment-cloud-guide/module-07/app
# Login
railway login
# Crear proyecto
railway init
# Nombre: docusearch-ai-railway
# Configurar variables
railway variables set OPENAI_API_KEY=sk-proj-xxx
railway variables set PLATFORM=railway
# Deploy
railway up
# Generar URL
railway domain
# Verificar
RAILWAY_URL=$(railway domain | grep "https")
curl $RAILWAY_URL/health
# {"status":"healthy","version":"1.0.0","platform":"railway"}
curl -X POST $RAILWAY_URL/ask \
-H "Content-Type: application/json" \
-d '{"question": "¿Qué ventajas tiene Railway sobre AWS?", "max_tokens": 200}'
# Ver logs
railway logs --num 20
Ejercicio 2: Agregar PostgreSQL y Redis con un comando
Agrega PostgreSQL y Redis a tu proyecto Railway y verifica que las variables se inyectan automáticamente.
Ver solución
# Agregar PostgreSQL
railway add
# Seleccionar: PostgreSQL
# Agregar Redis
railway add
# Seleccionar: Redis
# Verificar que las variables se inyectaron
railway variables
# DATABASE_URL=postgresql://...
# REDIS_URL=redis://...
# PGHOST=...
# PGPORT=...
# PGDATABASE=...
# PGUSER=...
# PGPASSWORD=...
# REDISHOST=...
# REDISPORT=...
# REDISUSER=...
# REDISPASSWORD=...
# Conectar directamente a la DB desde tu terminal
railway connect postgres
# psql (16.x)
# docusearch=> SELECT version();
# PostgreSQL 16.x ...
# Conectar a Redis
railway connect redis
# 127.0.0.1:6379> PING
# PONG
# Verificar desde la app que las variables funcionan
# Agregar endpoint de debug (solo para testing):
@app.get("/debug/db")
async def debug_db():
import psycopg2
conn = psycopg2.connect(os.environ["DATABASE_URL"])
cur = conn.cursor()
cur.execute("SELECT version()")
version = cur.fetchone()[0]
conn.close()
return {"postgres_version": version}
# Redeploy con el nuevo endpoint
railway up
# Verificar
curl $RAILWAY_URL/debug/db
# {"postgres_version":"PostgreSQL 16.x on ..."}
Ejercicio 3: Comparar tiempos de deploy — Render vs Railway
Escribe un script que mida el tiempo desde push hasta que la app responde en ambas plataformas. Documenta la diferencia.
Ver solución
# compare_deploy_times.py
import time
import subprocess
import requests
def measure_deploy_time(platform: str, url: str, push_command: str) -> dict:
"""Mide el tiempo desde push hasta que la app responde."""
print(f"\n=== {platform} ===")
print(f"Ejecutando push...")
start = time.time()
subprocess.run(push_command, shell=True, capture_output=True)
push_time = time.time() - start
print(f"Push completado en {push_time:.1f}s")
print("Esperando a que la app responda...")
max_wait = 600 # 10 minutos máximo
poll_interval = 10
elapsed = 0
while elapsed < max_wait:
try:
resp = requests.get(f"{url}/health", timeout=5)
if resp.status_code == 200:
total_time = time.time() - start
print(f"App online después de {total_time:.1f}s")
return {
"platform": platform,
"push_time_s": round(push_time, 1),
"total_deploy_time_s": round(total_time, 1),
"status": "success",
}
except requests.exceptions.RequestException:
pass
time.sleep(poll_interval)
elapsed += poll_interval
print(f" Esperando... ({elapsed}s)")
return {
"platform": platform,
"push_time_s": round(push_time, 1),
"total_deploy_time_s": max_wait,
"status": "timeout",
}
render_result = measure_deploy_time(
platform="Render",
url="https://docusearch-ai.onrender.com",
push_command="git push origin main",
)
railway_result = measure_deploy_time(
platform="Railway",
url="https://docusearch-ai-production.up.railway.app",
push_command="railway up",
)
print("\n=== Resultados ===")
print(f"Render: {render_result['total_deploy_time_s']}s")
print(f"Railway: {railway_result['total_deploy_time_s']}s")
diff = render_result["total_deploy_time_s"] - railway_result["total_deploy_time_s"]
faster = "Railway" if diff > 0 else "Render"
print(f"{faster} es {abs(diff):.0f}s más rápido")
## Resultados típicos
| Métrica | Render | Railway |
|---------|--------|---------|
| Push/upload | ~2s | ~5s (railway up empaqueta local) |
| Build | ~60-120s | ~30-90s |
| Deploy | ~30-60s | ~10-30s |
| **Total** | **~120-180s** | **~60-120s** |
Railway tiende a ser más rápido porque Nixpacks tiene cache agresivo
y el deploy pipeline es más corto. Render prioriza estabilidad sobre
velocidad en el pipeline.
Ejercicio 4: Configurar Preview Environment para tu app
Configura Preview Environments en Railway para que cada PR genere un deploy aislado con su propia base de datos.
Ver solución
# 1. En Railway Dashboard:
# Project → Settings → Environments
# - Enable PR Deploys: ON
# 2. Crear una rama de feature
git checkout -b feature/add-streaming
# Agregar endpoint de streaming a main.py
from fastapi.responses import StreamingResponse
import json
import asyncio
@app.post("/ask/stream")
async def ask_stream(query: Query):
async def generate():
api_key = os.environ.get("OPENAI_API_KEY")
import openai
client = openai.OpenAI(api_key=api_key)
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Eres un asistente de documentación."},
{"role": "user", "content": query.question},
],
max_tokens=query.max_tokens,
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
token = chunk.choices[0].delta.content
yield f"data: {json.dumps({'token': token})}\n\n"
await asyncio.sleep(0)
yield "data: [DONE]\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
# 3. Push la rama
git add -A
git commit -m "Add streaming endpoint"
git push origin feature/add-streaming
# 4. Crear PR en GitHub
gh pr create --title "Add streaming endpoint" --body "Agrega /ask/stream con SSE"
# 5. Railway detecta el PR y crea un preview automáticamente
# Dashboard muestra:
# Production: https://docusearch-ai-production.up.railway.app
# PR #1: https://docusearch-ai-pr-1.up.railway.app
# 6. Verificar el preview
curl https://docusearch-ai-pr-1.up.railway.app/health
# 7. El preview tiene su propia instancia de PostgreSQL y Redis
# Los datos de producción NO se afectan
# 8. Al mergear el PR, Railway destruye el preview automáticamente
Resumen
- Railway prioriza developer experience:
railway updespliega en segundos desde tu terminal. - Pricing basado en uso es flexible pero menos predecible que Render — una app 24/7 con 1 vCPU + 1 GB RAM cuesta ~$25-30/mes.
- Add-ons integrados (PostgreSQL, Redis) se agregan con un comando y las variables se inyectan automáticamente.
- Preview Environments son la feature estrella para equipos: cada PR genera un deploy aislado con su propia DB.
- Railway CLI es más potente que Render:
railway connect postgresabre psql directo a tu base de datos. - Request timeout de 5 minutos es mejor que Render (30s) para AI workloads con respuestas largas.
- Trial ($5 crédito) se consume rápido. Hobby ($5/mes + uso) es el mínimo viable.
- El patrón más rápido:
railway init+railway add(PostgreSQL) +railway variables set+railway up.
Recursos Adicionales
- Railway Documentation — Documentación oficial completa
- Railway CLI Reference — Referencia completa de comandos CLI
- Railway Templates — Templates pre-configurados para deploy rápido
- Railway Nixpacks — El build system que Railway usa por defecto
- Railway Pricing Calculator — Calculadora de pricing por uso
- Railway Changelog — Novedades y updates de la plataforma