Apariencia
0062. Topología de despliegue: Railway para el runtime, Cloudflare Pages para los estáticos
Estado
Aceptada
Contexto
Hasta 2026-08-07 el sistema no tenía despliegue: terminaba en main. Los cuatro clientes web/API corrían solo en local. Con el producto ya funcional (11 módulos de negocio, RBAC de tenant y de plataforma, outbox offline en mobile) hacía falta un runtime accesible para mostrarlo y, eventualmente, operarlo con clientes reales.
El proyecto es polyrepo con cinco repositorios de naturaleza distinta (ADR-0004): un servicio con estado (camaroneras_backend), tres artefactos estáticos (camaroneras_admin, camaroneras_platform, camaroneras_docs) y un binario móvil que no se "despliega" sino que se distribuye.
Equipo: una persona. Sin experiencia previa de operaciones. Presupuesto contenido.
Decisión
Repartir según la naturaleza de cada artefacto, no por comodidad de tener todo junto
camaroneras_backend→ Railway, como imagen Docker, junto a Railway Postgres y Railway Redis en la misma región (US East / Virginia), comunicados por la red privada.camaroneras_admin,camaroneras_platform,camaroneras_docs→ Cloudflare Pages, build automático desdemain.
Meter los estáticos en el PaaS de contenedores habría significado pagar tres servicios por algo que un CDN sirve gratis, mejor y más cerca del usuario.
Todo bajo subdominios de micamaronera.com
No es preferencia estética. admin. llamando a api. es cross-origin pero same-site, lo que permite conservar SameSite=Lax en la cookie httpOnly del refresh token (ADR-0014). Con dominios distintos (*.pages.dev → *.up.railway.app) habría que bajar a SameSite=None, más débil y frágil, con un síntoma engañoso: el login funciona pero la sesión se pierde en cada F5.
Migraciones como paso de pre-deploy, no en el arranque de la app
npx prisma migrate deploy corre antes de levantar la versión nueva. Si falla, el deploy se aborta y la versión anterior sigue sirviendo. Meterlo en el arranque de la app habría acoplado el esquema al ciclo de vida del proceso y, con varias instancias, generado carreras entre ellas.
Corolario obligatorio: ninguna migración destructiva en un solo release. Se expande en uno y se contrae en otro posterior. Sin eso no hay rollback posible, porque volver a la imagen anterior no revierte el esquema.
La capa de datos migra a Neon + Upstash, con disparador explícito
Se documenta ahora y se ejecuta después: cuando los datos de un cliente real vivan en producción. El motivo es concreto: los backups de Railway son snapshots, no point-in-time recovery. Con la base vacía o con datos de demo eso alcanza (se resetea y se resiembra); con información que no se puede regenerar, no.
La migración es barata a propósito: dos variables de entorno y un pg_dump/pg_restore, sin cambios de código, porque ambas conexiones son connection strings (ADR-0063).
Alternativas descartadas
Todo en Railway, incluidos los estáticos. Cumple el deseo de "un solo panel", pero cobra tres servicios por sitios que no necesitan servidor y los sirve desde una única región en vez de un CDN de borde.
Render. Estáticos gratis y PITR nativo en Postgres — mejor en ese punto que Railway. Se descartó por los cold starts del tier bajo: una app de campo donde el técnico abre la pantalla y espera treinta segundos es una app que no se usa. La ventaja de PITR se recupera igual con Neon cuando llegue el disparador.
VPS con Coolify o Dokploy. Costo fijo bajísimo (~US$5/mes) y control total. Se descartó porque convierte al único desarrollador en el SRE: parches de SO, backups que hay que configurar y probar restaurar, monitoreo, seguridad. El día que el sistema se caiga un domingo, no hay a quién escalarle.
Kubernetes. Nunca estuvo en consideración seria. Un servicio, un desarrollador, sin picos de carga. La complejidad operativa se paga todos los días y no devuelve nada a esta escala.
Consecuencias
- El sistema tiene una URL pública y verificable:
https://api.micamaronera.com/api/v1/health. - El despliegue es automático al mergear a
main, en los cuatro repos. - Sin observabilidad: no hay filtro global de excepciones ni tabla de auditoría; los errores mueren en el stdout de Railway. Se asume conscientemente y es la tarea siguiente (
platform-logs, ya en el backlog). - Sin entorno de staging: se difirió porque producción arranca con cero datos, así que staging protegería algo que no existe. Railway permite clonar un environment, de modo que sumarlo después es barato. Disparador: antes del primer cliente real.
- La restricción de migraciones no destructivas pasa a ser permanente y aplica a toda tarea futura que toque el esquema.
camaroneras_mobilequeda fuera: la distribución móvil depende de decisiones no técnicas (cuenta Apple Developer, custodia del keystore de Android) y no bloquea el deploy web.