Despliegue con Caddy y Docker

Este documento describe el proceso completo de despliegue de UnoSportClub utilizando Caddy como reverse proxy y Docker para contenedorización, incluyendo configuraciones para entornos QA, Staging y Production.

Arquitectura de Despliegue

Componentes del Sistema

El sistema está compuesto por los siguientes componentes desplegados como contenedores Docker:

  • Backend (Functions): API REST implementada en Node.js/Express

  • Frontend Applications: 4 aplicaciones Angular independientes

  • unosport-app: Aplicación principal para usuarios finales

  • unosport-panel: Panel de operador/administrador

  • unosport-trainer: Panel de entrenador

  • unosport-sudo: Panel de super administrador

  • Caddy: Reverse proxy y load balancer con terminación SSL/TLS automática

  • PostgreSQL: Base de datos relacional (solo para Staging y QA, Production usa Cloud SQL externa)

Entornos de Despliegue

El sistema soporta tres entornos independientes:

  • Production: Ambiente de producción con datos reales

  • Staging: Ambiente de pruebas previo a producción

  • QA: Ambiente de control de calidad y testing

Cada entorno tiene: * Sus propios contenedores Docker * Su propia base de datos PostgreSQL (Staging y QA) * Sus propios subdominios * Configuraciones independientes

Configuración de Docker Compose

Estructura del docker-compose.yml

El archivo docker-compose.yml define todos los servicios necesarios:

version: '3.8'

services:
  # Production Backend
  unosport-backend:
    image: ghcr.io/cortex-ia-com-co/unosportclub-functions:latest
    pull_policy: always
    restart: unless-stopped
    user: 10000:10000
    volumes:
      - ./unosportclub/firebase/firebase-adminsdk.json:/firebase-adminsdk.json:ro
      - ./unosportclub/.env:/app/.env:ro
    networks:
      - n8n_frontend

  # Production Frontend Applications
  unosport-app:
    image: ghcr.io/cortex-ia-com-co/unosportclub:v0.0.1-p1
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  unosport-panel:
    image: ghcr.io/cortex-ia-com-co/unosportclub-panel:v0.1.8
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  unosport-trainer:
    image: ghcr.io/cortex-ia-com-co/unosportclub-trainer:latest
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  unosport-sudo:
    image: ghcr.io/cortex-ia-com-co/unosportclub-sudo:latest
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  # Staging Environment
  unosport-db-staging:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${DB_NAME:-unosportclub_staging}
      POSTGRES_USER: ${DB_USER:-unosport_admin}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - unosport_db_staging:/var/lib/postgresql/data
    networks:
      - n8n_frontend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-unosport_admin}"]
      interval: 10s
      timeout: 5s
      retries: 5

  unosport-backend-staging:
    image: ghcr.io/cortex-ia-com-co/unosportclub-functions:staging
    pull_policy: always
    restart: unless-stopped
    user: 10000:10000
    depends_on:
      unosport-db-staging:
        condition: service_healthy
    environment:
      NODE_ENV: staging
      DATABASE_URL: postgresql://${DB_USER:-unosport_admin}:${DB_PASSWORD}@unosport-db-staging:5432/${DB_NAME:-unosportclub_staging}
      DB_HOST: unosport-db-staging
      DB_PORT: 5432
      DB_NAME: ${DB_NAME:-unosportclub_staging}
      DB_USER: ${DB_USER:-unosport_admin}
      DB_PASSWORD: ${DB_PASSWORD}
      DB_SSL: "false"
      GOOGLE_APPLICATION_CREDENTIALS: /firebase-adminsdk.json
      FIREBASE_PROJECT_ID: unosportclubdev
      FIREBASE_REGION: us-central1
    volumes:
      - ./unosportclub/firebase/firebase-adminsdk.json:/firebase-adminsdk.json:ro
    command: >
      sh -c "
        cd /app &&
        node db/install.js &&
        node server_wrapper.js
      "
    networks:
      - n8n_frontend

  unosport-app-staging:
    image: ghcr.io/cortex-ia-com-co/unosportclub:staging
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  unosport-panel-staging:
    image: ghcr.io/cortex-ia-com-co/unosportclub-panel:staging
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  unosport-trainer-staging:
    image: ghcr.io/cortex-ia-com-co/unosportclub-trainer:staging
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  unosport-sudo-staging:
    image: ghcr.io/cortex-ia-com-co/unosportclub-sudo:staging
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  # QA Environment
  unosport-db-qa:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${DB_NAME_QA:-unosportclub_qa}
      POSTGRES_USER: ${DB_USER_QA:-unosport_admin}
      POSTGRES_PASSWORD: ${DB_PASSWORD_QA:-${DB_PASSWORD}}
    volumes:
      - unosport_db_qa:/var/lib/postgresql/data
    networks:
      - n8n_frontend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER_QA:-unosport_admin}"]
      interval: 10s
      timeout: 5s
      retries: 5

  unosport-backend-qa:
    image: ghcr.io/cortex-ia-com-co/unosportclub-functions:staging
    pull_policy: always
    restart: unless-stopped
    user: 10000:10000
    depends_on:
      unosport-db-qa:
        condition: service_healthy
    environment:
      NODE_ENV: staging
      DATABASE_URL: postgresql://${DB_USER_QA:-unosport_admin}:${DB_PASSWORD_QA:-${DB_PASSWORD}}@unosport-db-qa:5432/${DB_NAME_QA:-unosportclub_qa}
      DB_HOST: unosport-db-qa
      DB_PORT: 5432
      DB_NAME: ${DB_NAME_QA:-unosportclub_qa}
      DB_USER: ${DB_USER_QA:-unosport_admin}
      DB_PASSWORD: ${DB_PASSWORD_QA:-${DB_PASSWORD}}
      DB_SSL: "false"
      GOOGLE_APPLICATION_CREDENTIALS: /firebase-adminsdk.json
      FIREBASE_PROJECT_ID: unosportclubdev
      FIREBASE_REGION: us-central1
    volumes:
      - ./unosportclub/firebase/firebase-adminsdk.json:/firebase-adminsdk.json:ro
    command: >
      sh -c "
        cd /app &&
        node db/install.js &&
        node server_wrapper.js
      "
    networks:
      - n8n_frontend

  unosport-app-qa:
    image: ghcr.io/cortex-ia-com-co/unosportclub:staging
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  unosport-panel-qa:
    image: ghcr.io/cortex-ia-com-co/unosportclub-panel:staging
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  unosport-trainer-qa:
    image: ghcr.io/cortex-ia-com-co/unosportclub-trainer:staging
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  unosport-sudo-qa:
    image: ghcr.io/cortex-ia-com-co/unosportclub-sudo:staging
    pull_policy: always
    restart: unless-stopped
    networks:
      - n8n_frontend

  # Caddy Reverse Proxy
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    user: 10000:10000
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy_data:/data
      - ./caddy_config:/config
    networks:
      - n8n_frontend

networks:
  n8n_frontend:

volumes:
  unosport_db_staging:
    driver: local
  unosport_db_qa:
    driver: local

Características Importantes

Health Checks

Los servicios de base de datos incluyen health checks para asegurar que estén listos antes de iniciar los servicios dependientes:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-unosport_admin}"]
  interval: 10s
  timeout: 5s
  retries: 5

Dependencias entre Servicios

Los servicios de backend dependen de que la base de datos esté saludable:

depends_on:
  unosport-db-staging:
    condition: service_healthy

Inicialización de Base de Datos

Los servicios de backend ejecutan automáticamente las migraciones al iniciar:

command: >
  sh -c "
    cd /app &&
    node db/install.js &&
    node server_wrapper.js
  "

Configuración de Caddy

Estructura del Caddyfile

El archivo Caddyfile define el enrutamiento para todos los entornos:

# Production Environment
app.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend:3000
    reverse_proxy unosport-app:80
}

entrenador.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend:3000
    reverse_proxy unosport-trainer:80
}

panel.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend:3000
    reverse_proxy unosport-panel:80
}

control.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend:3000
    reverse_proxy unosport-sudo:80
}

# Staging Environment
app.staging.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend-staging:3000
    reverse_proxy unosport-app-staging:80
}

entrenador.staging.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend-staging:3000
    reverse_proxy unosport-trainer-staging:80
}

panel.staging.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend-staging:3000
    reverse_proxy unosport-panel-staging:80
}

control.staging.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend-staging:3000
    reverse_proxy unosport-sudo-staging:80
}

# QA Environment
app.qa.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend-qa:3000
    reverse_proxy unosport-app-qa:80
}

entrenador.qa.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend-qa:3000
    reverse_proxy unosport-trainer-qa:80
}

panel.qa.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend-qa:3000
    reverse_proxy unosport-panel-qa:80
}

control.qa.unosportclub.com.co {
    encode zstd gzip
    reverse_proxy /api* unosport-backend-qa:3000
    reverse_proxy unosport-sudo-qa:80
}

Características de Caddy

Compresión

Caddy aplica compresión automática usando zstd y gzip:

encode zstd gzip

Enrutamiento de API

Las rutas que comienzan con /api* se enrutan al backend:

reverse_proxy /api* unosport-backend:3000

Enrutamiento de Frontend

Todas las demás rutas se enrutan a la aplicación frontend:

reverse_proxy unosport-app:80

Certificados SSL/TLS

Caddy obtiene y renueva automáticamente certificados SSL/TLS de Let’s Encrypt para todos los dominios configurados.

Variables de Entorno

Archivo .env

El archivo .env en /opt/n8n/unosportclub/.env contiene la configuración de producción:

# Configuración de Base de Datos - Functions
DATABASE_URL="postgresql://unosportclub:GtTFWfP39IA643IrV1GxWJ5DVJAmBIif@pegasus.cortex-ia.com.co/unosportclub"

GOOGLE_APPLICATION_CREDENTIALS=/firebase-adminsdk.json

PORT=3000
HOST=0.0.0.0

Variables para Staging y QA

Las variables de entorno para Staging y QA se definen directamente en docker-compose.yml:

environment:
  NODE_ENV: staging
  DATABASE_URL: postgresql://${DB_USER:-unosport_admin}:${DB_PASSWORD}@unosport-db-staging:5432/${DB_NAME:-unosportclub_staging}
  DB_HOST: unosport-db-staging
  DB_PORT: 5432
  DB_NAME: ${DB_NAME:-unosportclub_staging}
  DB_USER: ${DB_USER:-unosport_admin}
  DB_PASSWORD: ${DB_PASSWORD}
  DB_SSL: "false"
  GOOGLE_APPLICATION_CREDENTIALS: /firebase-adminsdk.json
  FIREBASE_PROJECT_ID: unosportclubdev
  FIREBASE_REGION: us-central1

Proceso de Despliegue

Despliegue Inicial

  1. Clonar configuración de despliegue:

    cd /opt/n8n
    git clone <repository-url>
    cd unosportclub
  2. Configurar variables de entorno:

    cp .env.example .env
    # Editar .env con credenciales reales
  3. Configurar credenciales de Firebase:

    # Colocar firebase-adminsdk.json en ./unosportclub/firebase/
  4. Iniciar servicios:

    docker-compose up -d

Actualización de Servicios

Para actualizar un servicio específico:

# Actualizar backend de producción
docker-compose pull unosport-backend
docker-compose up -d unosport-backend

# Actualizar aplicación frontend
docker-compose pull unosport-app
docker-compose up -d unosport-app

# Actualizar todos los servicios de un entorno
docker-compose pull unosport-backend-staging unosport-app-staging unosport-panel-staging unosport-trainer-staging unosport-sudo-staging
docker-compose up -d unosport-backend-staging unosport-app-staging unosport-panel-staging unosport-trainer-staging unosport-sudo-staging

Reinicio de Servicios

# Reiniciar un servicio específico
docker-compose restart unosport-backend

# Reiniciar todos los servicios de un entorno
docker-compose restart unosport-backend-staging unosport-app-staging unosport-panel-staging unosport-trainer-staging unosport-sudo-staging

# Reiniciar todos los servicios
docker-compose restart

Verificación de Estado

# Ver estado de todos los servicios
docker-compose ps

# Ver logs de un servicio
docker-compose logs -f unosport-backend

# Ver logs de todos los servicios de un entorno
docker-compose logs -f unosport-backend-staging unosport-app-staging

Gestión de Base de Datos

Migraciones Automáticas

Los servicios de backend ejecutan automáticamente las migraciones al iniciar mediante el comando:

node db/install.js

Este script: * Verifica la conexión a la base de datos * Ejecuta migraciones pendientes * Carga seeders si es necesario

Migración Inicial Manual dentro del Contenedor

En algunos casos, puede ser necesario ejecutar las migraciones manualmente dentro del contenedor, especialmente cuando:

  • Las migraciones automáticas fallaron durante el inicio

  • Necesitas ejecutar migraciones en un contenedor ya corriendo

  • Quieres verificar el estado de las migraciones antes de iniciar el servidor

  • Necesitas ejecutar migraciones sin reiniciar el servicio

Ejecutar Migraciones en Contenedor Corriendo

Para ejecutar migraciones en un contenedor que ya está corriendo:

# Staging - Ejecutar migraciones dentro del contenedor
docker-compose exec unosport-backend-staging sh -c "cd /app && node db/install.js"

# QA - Ejecutar migraciones dentro del contenedor
docker-compose exec unosport-backend-qa sh -c "cd /app && node db/install.js"
Verificar Estado de Migraciones

Para verificar qué migraciones se han ejecutado:

# Conectarse al contenedor de backend
docker-compose exec unosport-backend-staging sh

# Dentro del contenedor, ejecutar:
cd /app
node -e "const {pool} = require('./db/index'); pool.query('SELECT * FROM schema_migrations ORDER BY id').then(r => {console.table(r.rows); pool.end();})"

O usando psql directamente en el contenedor de base de datos:

# Ver migraciones ejecutadas en Staging
docker-compose exec unosport-db-staging psql -U unosport_admin -d unosportclub_staging -c "SELECT * FROM schema_migrations ORDER BY id;"

# Ver migraciones ejecutadas en QA
docker-compose exec unosport-db-qa psql -U unosport_admin -d unosportclub_qa -c "SELECT * FROM schema_migrations ORDER BY id;"
Ejecutar Migraciones sin Seeders

Si necesitas ejecutar solo las migraciones sin los seeders:

# Staging - Solo migraciones
docker-compose exec unosport-backend-staging sh -c "cd /app && node -e \"const {runMigrations, pool} = require('./db/index'); runMigrations().then(() => pool.end())\""

# QA - Solo migraciones
docker-compose exec unosport-backend-qa sh -c "cd /app && node -e \"const {runMigrations, pool} = require('./db/index'); runMigrations().then(() => pool.end())\""
Ejecutar Migraciones en Contenedor Nuevo (antes de iniciar servidor)

Si necesitas ejecutar migraciones antes de iniciar el servidor, puedes usar un contenedor temporal:

# Staging - Ejecutar migraciones con contenedor temporal
docker-compose run --rm unosport-backend-staging sh -c "cd /app && node db/install.js"

# QA - Ejecutar migraciones con contenedor temporal
docker-compose run --rm unosport-backend-qa sh -c "cd /app && node db/install.js"

El flag --rm elimina automáticamente el contenedor después de ejecutar el comando.

Verificar Logs de Migración

Para ver los logs detallados de la ejecución de migraciones:

# Ver logs del backend durante inicio (muestra migraciones automáticas)
docker-compose logs unosport-backend-staging | grep -i migration

# Ver logs completos del backend
docker-compose logs unosport-backend-staging

# Seguir logs en tiempo real
docker-compose logs -f unosport-backend-staging
Solución de Problemas Comunes

Error: "Cannot connect to database"

Verifica que la base de datos esté corriendo y saludable:

# Verificar estado de base de datos
docker-compose ps unosport-db-staging

# Verificar salud de base de datos
docker-compose exec unosport-db-staging pg_isready -U unosport_admin

# Verificar variables de entorno en el contenedor
docker-compose exec unosport-backend-staging env | grep DB_

Error: "Migration already executed"

Este es un comportamiento normal. El sistema registra las migraciones ejecutadas en la tabla schema_migrations y no las ejecuta dos veces.

Error: "Permission denied"

Verifica que el usuario del contenedor tenga permisos adecuados. El contenedor se ejecuta con user: 10000:10000 por seguridad.

Re-ejecutar migraciones fallidas

Si una migración falló parcialmente, puedes verificar el estado y ejecutar manualmente:

# Ver estado de migraciones
docker-compose exec unosport-db-staging psql -U unosport_admin -d unosportclub_staging -c "SELECT * FROM schema_migrations ORDER BY id;"

# Ejecutar migraciones manualmente
docker-compose exec unosport-backend-staging sh -c "cd /app && node db/install.js"

Acceso a Base de Datos

Para acceder a una base de datos de un entorno:

# Staging
docker-compose exec unosport-db-staging psql -U unosport_admin -d unosportclub_staging

# QA
docker-compose exec unosport-db-qa psql -U unosport_admin -d unosportclub_qa

Backup de Base de Datos

# Backup de Staging
docker-compose exec unosport-db-staging pg_dump -U unosport_admin unosportclub_staging > backup_staging_$(date +%Y%m%d_%H%M%S).sql

# Backup de QA
docker-compose exec unosport-db-qa pg_dump -U unosport_admin unosportclub_qa > backup_qa_$(date +%Y%m%d_%H%M%S).sql

Restauración de Base de Datos

# Restaurar Staging
docker-compose exec -T unosport-db-staging psql -U unosport_admin unosportclub_staging < backup_staging_20260101_120000.sql

# Restaurar QA
docker-compose exec -T unosport-db-qa psql -U unosport_admin unosportclub_qa < backup_qa_20260101_120000.sql

Monitoreo y Logs

Logs de Docker Compose

# Ver logs en tiempo real
docker-compose logs -f

# Ver logs de un servicio específico
docker-compose logs -f unosport-backend

# Ver últimas 100 líneas
docker-compose logs --tail=100 unosport-backend

Verificación de Salud

# Verificar estado de contenedores
docker-compose ps

# Verificar salud de base de datos
docker-compose exec unosport-db-staging pg_isready -U unosport_admin

# Verificar conectividad de red
docker-compose exec unosport-backend ping unosport-db-staging

Seguridad

Usuario no-root

Todos los servicios se ejecutan con usuario no-root (user: 10000:10000) para mayor seguridad.

Volúmenes de Solo Lectura

Los archivos de configuración se montan como solo lectura (:ro):

volumes:
  - ./unosportclub/firebase/firebase-adminsdk.json:/firebase-adminsdk.json:ro
  - ./unosportclub/.env:/app/.env:ro

Redes Aisladas

Todos los servicios están en la misma red Docker (n8n_frontend) para comunicación interna, pero aislados del host.

Certificados SSL/TLS

Caddy gestiona automáticamente los certificados SSL/TLS mediante Let’s Encrypt, asegurando conexiones cifradas.

Solución de Problemas

Servicio No Inicia

# Verificar logs
docker-compose logs unosport-backend-staging

# Verificar configuración
docker-compose config

# Verificar dependencias
docker-compose ps unosport-db-staging

Error de Conexión a Base de Datos

# Verificar que la base de datos esté saludable
docker-compose exec unosport-db-staging pg_isready -U unosport_admin

# Verificar variables de entorno
docker-compose exec unosport-backend-staging env | grep DB_

# Verificar conectividad de red
docker-compose exec unosport-backend-staging ping unosport-db-staging

Error de Certificado SSL

# Verificar logs de Caddy
docker-compose logs caddy

# Verificar configuración de Caddyfile
docker-compose exec caddy caddy validate --config /etc/caddy/Caddyfile

# Reiniciar Caddy
docker-compose restart caddy

Limpiar y Reconstruir

# Detener y eliminar contenedores
docker-compose down

# Eliminar volúmenes (CUIDADO: elimina datos)
docker-compose down -v

# Reconstruir e iniciar
docker-compose up -d --force-recreate

Mejores Prácticas

Gestión de Versiones

  • Usar tags específicos en lugar de latest para producción

  • Probar actualizaciones en QA antes de Staging

  • Probar en Staging antes de Production

Backups Regulares

  • Realizar backups diarios de bases de datos

  • Almacenar backups en ubicación segura

  • Probar restauración de backups periódicamente

Monitoreo Continuo

  • Configurar alertas para servicios caídos

  • Monitorear uso de recursos (CPU, memoria, disco)

  • Revisar logs regularmente para detectar problemas

Actualizaciones de Seguridad

  • Mantener imágenes Docker actualizadas

  • Revisar vulnerabilidades regularmente

  • Aplicar parches de seguridad rápidamente