Deployment
Tabla de Contenidos
- Vision General del Pipeline
- Coolify VPS (DEV/TEST)
- Railway Pro (Produccion)
- Cloudflare Pages (Docs + Landing)
- CI/CD GitLab
- Dockerfiles y Multi-Stage Builds
- Rollback
1. Vision General del Pipeline
El deployment de Nvito combina multiples herramientas segun 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 automaticamente.
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 |
| Landing (nvito.mx) | — | Cloudflare Pages | git push a main |
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 |
| Region | 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 (automatico 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 |
2.3 Flujo de Deploy en Coolify
2.4 Conexion con GitLab
Coolify v4 no tiene integracion nativa con GitLab (solo GitHub y Gitea). La conexion se hace via deploy keys SSH:
- Generar SSH key en Coolify: Security > Private Keys > Add
- Copiar la clave publica 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 automaticamente:
- 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 automaticamente 30 dias antes de expirar
- Cloudflare proxy (modo Full) confía en el certificado Let's Encrypt del origin
2.7 Sin Suspension
Docker en VPS no duerme los contenedores. Esto es critico porque nvito-api usa Bull queues con Redis para procesar emails, WhatsApp y jobs asincronos. Si el backend se suspende, los jobs en cola no se procesan.
3. Railway Pro (Produccion)
Railway Pro es la plataforma de hosting planificada para produccion. Ofrece deployment automatico, private networking y escalado vertical sin gestion de servidores.
3.1 Configuracion del Proyecto
3.2 Auto-Deploy desde GitLab
Railway soporta deployment automatico conectando el repositorio de GitLab:
- Vincular repo GitLab al servicio Railway
- Configurar branch de deploy:
main - Cada push a
maindispara build + deploy automatico - Railway detecta el Dockerfile y ejecuta multi-stage build
- Health check antes de cortar trafico al contenedor anterior
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 despues de 3 retries, Railway revierte al deployment anterior automaticamente.
3.5 Pricing Railway Pro
| Concepto | Costo |
|---|---|
| Plan Pro | $5/usuario/mes |
| Compute (estimado) | ~$0.000463/vCPU/min + $0.000231/GB/min |
| PostgreSQL | Incluido en compute |
| Networking | $0.10/GB egress (100 GB gratis/mes) |
| Estimacion mensual | $50-80 USD (6 servicios + PostgreSQL) |
4. Cloudflare Pages (Docs + Landing)
Los sitios estaticos de Nvito (documentacion y landing page) se despliegan en Cloudflare Pages, aprovechando la red edge global y el deploy automatico.
4.1 Proyectos en CF Pages
| Proyecto | Repositorio | Build Command | Output Dir | Custom Domain |
|---|---|---|---|---|
| nvito-docs | nvito-docs | npm run build | .next/standalone | docs.nvito.mx |
| nvito-landing | nvito-landing | npm run build | out | nvito.mx |
4.2 Configuracion de Build
4.3 Configuracion en Pages Dashboard
| Setting | Valor |
|---|---|
| Framework preset | Next.js (Static Export) |
| Node.js version | 20 |
| Build command | npm run build |
| Build output directory | .next/standalone (docs) o out (landing) |
| Root directory | / |
4.4 Custom Domain + CF Access
Para docs.nvito.mx:
- Configurar custom domain en CF Pages dashboard
- CF agrega CNAME automaticamente:
docs.nvito.mx→nvito-docs.pages.dev - Habilitar CF Access en el subdominio para restringir acceso
Preview deploys
Cloudflare Pages genera una URL de preview unica para cada commit y branch (ej. abc123.nvito-docs.pages.dev). Esto permite revisar cambios antes de merge a main sin afectar produccion.
5. CI/CD GitLab
5.1 Pipeline Stages
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.
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 demas 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 estan 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 reactivaran al escalar el equipo.
6. Dockerfiles y Multi-Stage Builds
Todos los proyectos de Nvito usan Dockerfiles multi-stage para producir imagenes minimas y seguras.
6.1 Patron Comun (3 Stages)
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 Practicas en los Dockerfiles
| Practica | Razon |
|---|---|
| node:20-alpine | Imagen base minima (~50 MB vs ~350 MB de node:20) |
| dumb-init | Manejo correcto de senales 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 | Instalacion reproducible desde lockfile |
| --ignore-scripts | Prevenir ejecucion 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
| Metodo | Como | Velocidad |
|---|---|---|
| Dashboard | Deployments > seleccionar version 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 via dashboard es instantaneo porque reutiliza la imagen Docker ya construida.
7.2 Coolify
| Metodo | Como | 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
| Metodo | Como | Velocidad |
|---|---|---|
| Dashboard | Deployments > seleccionar version > Rollback to this deploy | Segundos |
| Automatico | Cada commit genera un deployment inmutable con URL unica | — |
CF Pages mantiene todos los deployments historicos como snapshots inmutables. El rollback es instantaneo.
7.4 Base de Datos
Rollback de migraciones requiere cuidado
A diferencia del codigo, las migraciones de base de datos no se pueden revertir automaticamente. prisma migrate deploy aplica migraciones hacia adelante unicamente. Para revertir un cambio de schema, se debe crear una nueva migracion que deshaga los cambios.
| Escenario | Solucion |
|---|---|
| Migracion fallida (no completo) | Prisma marca la migracion como failed. Corregir el SQL y ejecutar prisma migrate resolve --applied {name} |
| Migracion exitosa pero codigo incompatible | Crear nueva migracion que revierta los cambios de schema |
| Perdida de datos | Restaurar desde backup (Neon: branching point-in-time, Railway: snapshots automaticos) |