Despliegue con Caddy y Docker

Este documento describe el despliegue de UnoSportClub con Docker y, opcionalmente, Caddy como proxy con terminación TLS.

El repositorio unosport-v0.4 (monorepo) expone una imagen unificada (raíz, Dockerfile): un solo contenedor con Nginx en el puerto 80 y Node.js (API) en el 5000 solo hacia dentro; Nginx enruta por nombre de host (Host), no por puertos distintos por aplicación. Más abajo, el apéndice Legado pre-v0.4 documenta despliegues anteriores (Firebase, multi-imagen).

Imagen unificada del monorepo

Arquitectura interna

  • Construcción: npm run build:production:all y build de Antora/docs; los artefactos quedan bajo /srv/static/…​ dentro de la imagen.

  • Nginx (EXPOSE 80): sirve cada SPA según server_name y proxifica hacia Node las rutas /api, /oauth/, /.well-known y /socket.io.

  • Node (PORT / FUNCTIONS_PORT, por defecto 5000): no debe publicarse al host; el tráfico de API llega vía Nginx en 80.

  • Plantilla: docker/nginx.conf.template sustituida en arranque por docker/docker-entrypoint.sh con envsubst (UNOSPORT_HOST_*, FUNCTIONS_PORT).

Variables de host obligatorias

Los FQDN deben coincidir con lo que resuelve DNS (o /etc/hosts) y con el proxy delante:

  • UNOSPORT_HOST_APP — aplicación principal (usuarios)

  • UNOSPORT_HOST_PANEL — panel operador

  • UNOSPORT_HOST_TRAINER — panel entrenador

  • UNOSPORT_HOST_SUDO — panel super usuario

  • UNOSPORT_HOST_ACCOUNTING — contabilidad

  • UNOSPORT_HOST_DOCS — sitio de documentación estática

Valores por defecto en el entrypoint: *.unosport.local (solo desarrollo local).

Variables de aplicación y secretos

Reutiliza las mismas variables que el backend en functions/ (ver functions/.env.example): PostgreSQL (DB_*, DB_NAME_ACCOUNTING, DB_SSL), OAuth2 (OAUTH2_ISSUER_BASE_URL, OAUTH2_AUDIENCE, OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, SALT_SEPARATOR, SIGNER_KEY), BOT_API_KEYS, PUSHER_*, SMTP, etc.

Con HTTPS delante, OAUTH2_ISSUER_BASE_URL debe ser la URL pública del issuer (p. ej. https://app.ejemplo.com/), alineada con el frontend y el proxy.

Ejemplo docker-compose (un servicio de aplicación)

Sustituye la imagen por el tag que publique CI (p. ej. GHCR). PostgreSQL puede seguir siendo un servicio aparte.

services:
  unosport-qa:
    image: ghcr.io/cortex-ia-com-co/unosportclub:staging
    pull_policy: always
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      PORT: "5000"
      FUNCTIONS_PORT: "5000"
      NODE_ENV: staging
      UNOSPORT_HOST_APP: app.qa.ejemplo.com
      UNOSPORT_HOST_PANEL: panel.qa.ejemplo.com
      UNOSPORT_HOST_TRAINER: entrenador.qa.ejemplo.com
      UNOSPORT_HOST_SUDO: control.qa.ejemplo.com
      UNOSPORT_HOST_ACCOUNTING: accounting.qa.ejemplo.com
      UNOSPORT_HOST_DOCS: docs.qa.ejemplo.com
      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_NAME_ACCOUNTING: ${QA_DB_NAME_ACCOUNTING}
      DB_SSL: "false"
      OAUTH2_ISSUER_BASE_URL: ${OAUTH2_PUBLIC_ISSUER}
      OAUTH2_AUDIENCE: ${OAUTH2_AUDIENCE}
      OAUTH2_CLIENT_ID: ${OAUTH2_CLIENT_ID}
      OAUTH2_CLIENT_SECRET: ${OAUTH2_CLIENT_SECRET}
      SALT_SEPARATOR: ${SALT_SEPARATOR}
      SIGNER_KEY: ${SIGNER_KEY}
      BOT_API_KEYS: ${BOT_API_KEYS}
    depends_on:
      unosport-db-qa:
        condition: service_healthy
    networks:
      - n8n_frontend
    healthcheck:
      test: ["CMD", "wget", "-q", "-O-", "http://127.0.0.1/api"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 60s

Notas:

  • Si la imagen no incluye wget, instálalo en el Dockerfile o usa curl equivalente en test.

  • user: 10000:10000: el entrypoint oficial ejecuta Nginx; comprobad compatibilidad con permisos de /var/lib/nginx antes de forzar usuario no root.

  • El entrypoint de la imagen unificada no ejecuta node db/install.js; las migraciones deben lanzarse con docker compose exec o un job de despliegue (ver Migraciones con la imagen unificada).

Migraciones con la imagen unificada

docker compose exec unosport-qa sh -c "cd /app/functions && node db/install.js"

Ejecutad esto tras el primer despliegue o cuando haya migraciones nuevas, con la base de datos accesible desde las variables DB_*.

Caddy delante de la imagen unificada

Un solo upstream al puerto 80 del servicio; Caddy reenvía el header Host y Nginx interno elige la SPA. No hace falta enrutar /api a otro contenedor.

app.qa.ejemplo.com,
panel.qa.ejemplo.com,
trainer.qa.ejemplo.com,
sudo.qa.ejemplo.com,
accounting.qa.ejemplo.com,
docs.qa.ejemplo.com {
	encode zstd gzip
	request_body {
		max_size 10MB
	}
	reverse_proxy unosport-qa:80
}

Si Caddy corre en el host y el contenedor publica 8080:80, usad 127.0.0.1:8080 en lugar de unosport-qa:80.

Verificación rápida

curl -sS -H "Host: app.qa.ejemplo.com" http://127.0.0.1:8080/api

Debe responder JSON de estado de la API.

Arquitectura de Despliegue

Componentes del Sistema

Modelo unificado (actual en monorepo)

  • Contenedor único: Nginx + API Node + todos los builds Angular y docs estáticos

  • Caddy (opcional): TLS y proxy reverso hacia el puerto publicado 80 del contenedor

  • PostgreSQL: base de datos (contenedor dedicado o gestionada)

Modelo legado (varias imágenes)

Si aún usáis imágenes separadas por repositorio o tag:

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

  • Frontend: aplicaciones Angular en imágenes distintas (unosportclub, panel, trainer, sudo, accounting, …)

  • Caddy: reverse proxy hacia cada servicio

  • PostgreSQL: relacional (Staging/QA o Cloud SQL en producción)

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 (modelo legado)

El siguiente ejemplo asume varias imágenes (API + un contenedor por frontend). Para nuevos despliegues desde el monorepo, preferid la imagen unificada.

Estructura del docker-compose.yml

El archivo docker-compose.yml define todos los servicios necesarios en el modelo multi-contenedor:

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-qa:
    image: ghcr.io/cortex-ia-com-co/unosportclub:staging
    pull_policy: always
    restart: unless-stopped
    depends_on:
      unosport-db-qa:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-q", "-O-", "http://127.0.0.1/api"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 60s
    environment:
      PORT: "5000"
      FUNCTIONS_PORT: "5000"
      NODE_ENV: staging
      UNOSPORT_HOST_APP: app.qa.unosportclub.com.co
      UNOSPORT_HOST_PANEL: panel.qa.unosportclub.com.co
      UNOSPORT_HOST_TRAINER: entrenador.qa.unosportclub.com.co
      UNOSPORT_HOST_SUDO: control.qa.unosportclub.com.co
      UNOSPORT_HOST_ACCOUNTING: accounting.qa.unosportclub.com.co
      UNOSPORT_HOST_DOCS: docs.qa.unosportclub.com.co
      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"
      OAUTH2_ISSUER_BASE_URL: ${OAUTH2_PUBLIC_ISSUER}
    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

Imagen unificada: un upstream por entorno

Con el contenedor descrito en Imagen unificada del monorepo, todas las aplicaciones comparten el mismo puerto 80 dentro del contenedor; el proxy solo debe preservar el header Host. Ejemplo (QA):

app.qa.unosportclub.com.co,
panel.qa.unosportclub.com.co,
entrenador.qa.unosportclub.com.co,
control.qa.unosportclub.com.co,
accounting.qa.unosportclub.com.co,
docs.qa.unosportclub.com.co {
	encode zstd gzip
	request_body {
		max_size 10MB
	}
	reverse_proxy unosport-qa:80
}

Los nombres deben coincidir con UNOSPORT_HOST_* del contenedor.

Estructura del Caddyfile (modelo legado)

El archivo Caddyfile define el enrutamiento cuando API y frontends son servicios Docker separados (enrutar /api* al backend y el resto a cada frontend):

# 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 (en el repositorio: imagen unificada; un bloque por host, mismo upstream)
app.qa.unosportclub.com.co {
    encode zstd gzip
    request_body {
        max_size 10MB
    }
    reverse_proxy unosport-qa:80
}
entrenador.qa.unosportclub.com.co {
    encode zstd gzip
    request_body {
        max_size 10MB
    }
    reverse_proxy unosport-qa:80
}
# panel, control, accounting, docs: mismo patrón hacia unosport-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 (imagen unificada) — código en /app/functions
docker-compose exec unosport-qa sh -c "cd /app/functions && 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-qa sh -c "cd /app/functions && 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-qa sh -c "cd /app/functions && 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

Referencias