1. Visión General del Pipeline
El deployment de Nvito combina múltiples herramientas según el ambiente y el tipo de servicio. El flujo general es: el desarrollador hace push, GitLab CI valida calidad, y el proveedor de hosting despliega automáticamente.
Pipeline de Deployment
Destinos
Matriz de Deployment
| Proyecto | DEV/TEST | PROD | Trigger |
|---|---|---|---|
| nvito-api | Coolify (Docker) | Railway Pro | GitLab webhook / git push |
| nvito-admin | Coolify (Docker) | Railway Pro | GitLab webhook / git push |
| nvito-invitations | Coolify (Docker) | Railway Pro | GitLab webhook / git push |
| nvito-pwa | Coolify (Docker) | Railway Pro | GitLab webhook / git push |
| nvito-docs | — | Cloudflare Pages | git push a main |
| nvito-landing | Coolify (Docker) | Cloudflare Pages (PROD) | GitLab webhook / git push |
2. Coolify VPS (DEV/TEST)
Coolify v4 es un PaaS open-source que gestiona contenedores Docker en el VPS de Contabo. Funciona como un "Heroku self-hosted".
2.1 Infraestructura del VPS
| Aspecto | Detalle |
|---|---|
| Proveedor | Contabo Cloud VPS 10 |
| Specs | 4 vCPU, 8 GB RAM, 150 GB SSD |
| Región | United States (Central) |
| OS | Ubuntu 22.04 LTS |
| Costo | $6.15 USD/mes ($4.95 + $1.20 region) |
| IP | 212.28.185.197 |
| Reverse Proxy | Traefik (integrado en Coolify) |
| SSL | Let's Encrypt (automático via Traefik) |
2.2 Servicios Desplegados
| Servicio | Ambiente | Branch | Dominio | Puerto |
|---|---|---|---|---|
| nvito-api-dev | DEV | develop | dev-api.nvito.mx | 3000 |
| nvito-api-test | TEST | test | test-api.nvito.mx | 3000 |
| nvito-admin-dev | DEV | develop | dev-admin.nvito.mx | 5050 |
| nvito-admin-test | TEST | test | test-admin.nvito.mx | 5050 |
| nvito-invitations-dev | DEV | develop | dev-inv.nvito.mx | 3001 |
| nvito-invitations-test | TEST | test | test-inv.nvito.mx | 3001 |
| nvito-pwa-dev | DEV | develop | dev-app.nvito.mx | 3002 |
| nvito-pwa-test | TEST | test | test-app.nvito.mx | 3002 |
| nvito-landing-dev | DEV | develop | dev-landing.nvito.mx | 4000 |
| nvito-landing-test | TEST | test | test-landing.nvito.mx | 4000 |
2.3 Flujo de Deploy en Coolify
Deploy en Coolify
2.4 Conexión con GitLab
Coolify v4 no tiene integración nativa con GitLab (solo GitHub y Gitea). La conexión se hace via deploy keys SSH:
- Generar SSH key en Coolify: Security > Private Keys > Add
- Copiar la clave pública al repo en GitLab: Settings > Repository > Deploy Keys
- Configurar servicio en Coolify como "Private Repository (with Deploy Key)"
- Configurar webhook URL de Coolify en GitLab: Settings > Webhooks > Push events
2.5 Variables de Entorno en Coolify
Las variables se configuran directamente en el dashboard de Coolify por servicio. No se usan archivos .env ni BWS para el runtime de las aplicaciones en Coolify.
Variables NEXT_PUBLIC_* en build time
Las variables con prefijo NEXT_PUBLIC_ se inyectan en build time (durante docker build). Si se modifican en Coolify, es necesario hacer un redeploy (rebuild completo) para que tomen efecto. Las variables de servidor (sin prefijo) se leen en runtime y toman efecto al reiniciar el contenedor.
2.6 SSL y Dominios
Traefik gestiona SSL automáticamente:
- Coolify configura labels de Traefik en el contenedor con el dominio custom
- Traefik detecta el nuevo dominio y solicita certificado Let's Encrypt via ACME HTTP-01
- El certificado se renueva automáticamente 30 días antes de expirar
- Cloudflare proxy (modo Full) confía en el certificado Let's Encrypt del origin
2.7 Sin Suspensión
Docker en VPS no duerme los contenedores. Esto es crítico porque nvito-api usa Bull queues con Redis para procesar emails, WhatsApp y jobs asíncronos. Si el backend se suspende, los jobs en cola no se procesan.
3. Railway Pro (Producción)
Railway Pro es la plataforma de hosting planificada para producción. Ofrece deployment automático, private networking y escalado vertical sin gestión de servidores.
3.1 Configuración del Proyecto
Railway — nvito-prod
Servicios
Cloudflare Pages
Servicios
Datos
Servicios Externos
3.2 Auto-Deploy desde GitLab
Railway soporta deployment automático conectando el repositorio de GitLab:
- Vincular repo GitLab al servicio Railway
- Configurar branch de deploy:
main - Cada push a
maindispara build + deploy automático - Railway detecta el Dockerfile y ejecuta multi-stage build
- Health check antes de cortar trafico al contenedor anterior
Repos aun no conectados
Los 4 servicios en Railway están creados como "Empty Service". La conexión de repos GitLab para auto-deploy es una tarea pendiente (P3) que se completará antes del primer deploy a producción.
3.3 Custom Domains y CNAME
Para cada servicio en Railway se configura un custom domain que apunta via CNAME:
app.nvito.mx CNAME nvito-pwa-prod.up.railway.app
inv.nvito.mx CNAME nvito-inv-prod.up.railway.app
admin.nvito.mx CNAME nvito-admin-prod.up.railway.app
Para api.nvito.mx, el CNAME apunta al Cloudflare Worker (no directamente a Railway).
3.4 Health Checks
| Servicio | Endpoint | Intervalo | Timeout | Retries |
|---|---|---|---|---|
| nvito-api | GET /health | 10s | 5s | 3 |
| nvito-admin | GET / | 10s | 5s | 3 |
| nvito-invitations | GET / | 10s | 5s | 3 |
| nvito-pwa | GET / | 10s | 5s | 3 |
Si el health check falla después de 3 retries, Railway revierte al deployment anterior automáticamente.
3.5 Pricing Railway Pro
| Concepto | Costo |
|---|---|
| Plan Pro | $20/mes (incluye $20 crédito) |
| CPU | $20/vCPU/mes (equivalente) |
| RAM | $10/GB/mes |
| Volume Storage | $0.15/GB/mes |
| Egress | $0.05/GB |
| Private networking | GRATIS (100 Gbps) |
| Custom domains | GRATIS (ilimitados) |
| PostgreSQL | Incluido en compute (sin surcharge) |
| Estimacion mensual | ~$28.50 USD neto (4 servicios + PostgreSQL, después de $20 crédito) |
4. Cloudflare Pages (Docs + Landing)
Los sitios estáticos de Nvito (documentación y landing page) se despliegan en Cloudflare Pages, aprovechando la red edge global y el deploy automático.
4.1 Proyectos en CF Pages
| Proyecto | Repositorio | Stack | Build Command | Output Dir | Custom Domain |
|---|---|---|---|---|---|
| nvito-docs | nvito-docs | Next.js 16.1.6 | npm run build | .next/standalone | docs.nvito.mx |
| nvito-landing | nvito-landing | Astro 5.x + Tailwind CSS 4 | npm run build | dist | nvito.mx |
Landing page con adapter condicional
La landing page (nvito.mx) usa Astro 5.x con un adapter condicional: @astrojs/cloudflare para producción (Cloudflare Pages) y @astrojs/node para DEV/TEST (Docker en Coolify). La ruta /join/[token] es SSR (prerender = false) para servir el smart link de deeplinks. Las demás páginas siguen siendo estáticas.
4.2 Configuración de Build
Cloudflare Pages — Build y Deploy
4.3 Configuración en Pages Dashboard
nvito-docs:
| Setting | Valor |
|---|---|
| Framework preset | Next.js (Static Export) |
| Node.js versión | 20 |
| Build command | npm run build |
| Build output directory | .next/standalone |
nvito-landing (Astro — Producción en CF Pages):
| Setting | Valor |
|---|---|
| Framework preset | Astro |
| Node.js version | 20 |
| Build command | npm run build |
| Build output directory | dist |
| Adapter | @astrojs/cloudflare |
| Ruta SSR | /join/[token] (smart link para deeplinks) |
nvito-landing (Astro — DEV/TEST en Coolify):
| Setting | Valor |
|---|---|
| Deploy | Docker (Dockerfile multi-stage) |
| Adapter | @astrojs/node (via ASTRO_ADAPTER=node) |
| Puerto | 4000 |
| Variables | PUBLIC_PWA_URL, ASTRO_ADAPTER |
4.4 Custom Domain + CF Access
Para docs.nvito.mx:
- Conectar repositorio GitLab a Cloudflare Pages
- Configurar custom domain en CF Pages dashboard
- CF agrega CNAME automáticamente:
docs.nvito.mx→nvito-docs.pages.dev - Habilitar CF Access en el subdominio (requiere autenticación del equipo)
4.5 nvito-docs — Métricas de Build
| Métrica | Valor |
|---|---|
| Páginas generadas | 106 páginas estáticas |
| Documentos MDX | 86 bilingües (43 ES + 43 EN) |
| Índice Pagefind | 11,000+ palabras indexadas |
| Búsqueda | Pagefind (Cmd+K), filtrado por locale |
| i18n | Rutas /es/* y /en/*, LocaleSwitch, detección automática |
Preview deploys
Cloudflare Pages genera una URL de preview única para cada commit y branch (ej. abc123.nvito-docs.pages.dev). Esto permite revisar cambios antes de merge a main sin afectar producción.
5. CI/CD GitLab
5.1 Pipeline Stages
Pipeline CI/CD — Stages
Stage: quality
Stage: migrate
5.2 Stage Quality
Se ejecuta en cada push a develop, test, main y en merge requests. Contiene 2 jobs paralelos:
Job lint:
lint:
stage: quality
script:
- npm ci --cache $NPM_CONFIG_CACHE
- npm run lint
- npm run format:check
Job test:unit:
test:unit:
stage: quality
services:
- postgres:15-alpine
variables:
POSTGRES_DB: nvito_test
DATABASE_URL: "postgresql://test:test@postgres:5432/nvito_test"
script:
- npm ci --cache $NPM_CONFIG_CACHE
- npx prisma generate
- npx prisma migrate deploy
- npm run test:ci
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
expire_in: 30 days
5.3 Stage Migrate
Se ejecuta solo cuando hay cambios en prisma/migrations/**/*. Usa Bitwarden Secrets Manager (BWS) para obtener la DATABASE_URL del ambiente correspondiente.
Migraciones por Branch
5.4 Estrategia de Branching
| Rama | Ambiente | Deploy | Migraciones |
|---|---|---|---|
feature/* | — | No | No |
develop | DEV | Auto (Coolify) | Si (Neon DEV) |
test | TEST | Auto (Coolify) | Si (Neon TEST) |
main | PROD | Auto (Railway) | Si (Railway PG) |
Flujo:
feature/xyz → MR a develop → merge → deploy DEV
develop → merge a test → deploy TEST
test → merge a main → deploy PROD
5.5 Variables de CI/CD en GitLab
| Variable | Almacenada en | Protected | Masked |
|---|---|---|---|
BWS_ACCESS_TOKEN | GitLab CI/CD | Si | Si |
BWS_PROJECT_DEV | GitLab CI/CD | No | No |
BWS_PROJECT_TEST | GitLab CI/CD | No | No |
BWS_PROJECT_PROD | GitLab CI/CD | Si | No |
Todos los demás secretos (DATABASE_URL, API keys, tokens) se almacenan en Bitwarden Secrets Manager y se inyectan via bws run en runtime.
5.6 Estado Actual
Pipelines deshabilitados temporalmente
Los pipelines de GitLab CI están deshabilitados temporalmente en los 5 proyectos (workflow.rules: never) para ahorrar compute de shared runners. Los tests y validaciones se ejecutan localmente antes de cada push. Se reactivarán al escalar el equipo.
6. Dockerfiles y Multi-Stage Builds
Todos los proyectos de Nvito usan Dockerfiles multi-stage para producir imágenes mínimas y seguras.
6.1 Patrón Común (3 Stages)
Dockerfile Multi-Stage Build
Stage 1: deps
Stage 2: build
Stage 3: runtime
6.2 Ejemplo: nvito-api Dockerfile
# Stage 1: Install dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
# Stage 2: Build application
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx prisma generate
RUN npm run build
RUN npm prune --production
# Stage 3: Production runtime
FROM node:20-alpine AS runtime
RUN apk add --no-cache dumb-init
WORKDIR /app
USER node
COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/prisma ./prisma
COPY --from=build --chown=node:node /app/package.json ./
EXPOSE 3000
CMD ["dumb-init", "node", "dist/main.js"]
6.3 Ejemplo: nvito-admin Dockerfile (Next.js Standalone)
# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Stage 2: Build
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# NEXT_PUBLIC_* variables must be available at build time
ARG NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
RUN npm run build
# Stage 3: Runtime (standalone output)
FROM node:20-alpine AS runtime
RUN apk add --no-cache dumb-init
WORKDIR /app
USER node
COPY --from=build --chown=node:node /app/.next/standalone ./
COPY --from=build --chown=node:node /app/.next/static ./.next/static
COPY --from=build --chown=node:node /app/public ./public
EXPOSE 5050
ENV PORT=5050
CMD ["dumb-init", "node", "server.js"]
6.4 Buenas Prácticas en los Dockerfiles
| Práctica | Razón |
|---|---|
| node:20-alpine | Imagen base mínima (~50 MB vs ~350 MB de node:20) |
| dumb-init | Manejo correcto de señales POSIX (SIGTERM, SIGINT) para graceful shutdown |
| USER node | Ejecutar como usuario no-root para seguridad |
| Multi-stage | Imagen final no contiene devDependencies, source code ni herramientas de build |
| COPY selectivo | Solo copiar artefactos necesarios para runtime |
| npm ci | Instalación reproducible desde lockfile |
| --ignore-scripts | Prevenir ejecución de scripts postinstall no confiables en stage deps |
7. Rollback
Cada plataforma de hosting ofrece mecanismos de rollback en caso de deploy fallido.
7.1 Railway
| Método | Cómo | Velocidad |
|---|---|---|
| Dashboard | Deployments > seleccionar versión anterior > Redeploy | Segundos |
| CLI | railway up --detach --service {id} --commit {sha} | Segundos |
| Git revert | git revert HEAD && git push → auto-deploy | Minutos (requiere build) |
Railway mantiene historial de todos los deployments. El rollback vía dashboard es instantáneo porque reutiliza la imagen Docker ya construida.
7.2 Coolify
| Método | Cómo | Velocidad |
|---|---|---|
| Dashboard | Service > Deployments > Restart con commit anterior | Segundos |
| Manual | SSH al VPS, docker ps, rollback manual con imagen anterior | Minutos |
| Git revert | git revert HEAD && git push → webhook rebuild | Minutos |
7.3 Cloudflare Pages
| Método | Cómo | Velocidad |
|---|---|---|
| Dashboard | Deployments > seleccionar versión > Rollback to this deploy | Segundos |
| Automático | Cada commit genera un deployment inmutable con URL única | — |
CF Pages mantiene todos los deployments históricos como snapshots inmutables. El rollback es instantáneo.
7.4 Base de Datos
Rollback de migraciones requiere cuidado
A diferencia del código, las migraciones de base de datos no se pueden revertir automáticamente. prisma migrate deploy aplica migraciones hacia adelante únicamente. Para revertir un cambio de schema, se debe crear una nueva migración que deshaga los cambios.
| Escenario | Solución |
|---|---|
| Migración fallida (no completó) | Prisma marca la migración como failed. Corregir el SQL y ejecutar prisma migrate resolve --applied {name} |
| Migración exitosa pero código incompatible | Crear nueva migración que revierta los cambios de schema |
| Perdida de datos | Restaurar desde backup (Neon: branching point-in-time, Railway: snapshots automáticos) |