Academia Deltamontero · Documentación
Arquitectura

Infraestructura

Servidor de producción (contenedor LXC de Proxmox), Coolify, acceso por la red interna, Cloudflare y dominio planificados, entorno local, recursos y manejo de la hora.

Resumen

ElementoProducciónDesarrollo local
HostContenedor LXC de Proxmox 172246 de la empresa, con Docker y Coolify dentroEquipo del desarrollador con Docker
OrquestaciónCoolify (con su propio Traefik)docker compose
DirecciónHoy http://192.168.0.192 (red interna). Planificado: https://learning.deltamontero.nethttp://localhost:8080
Archivos de composedocker-compose.ymldocker-compose.yml + docker-compose.override.yml (se carga solo)
Variables de entornoCoolify → Environment VariablesArchivo .env (no se sube al repositorio)
TLSHoy sin TLS (HTTP en la red interna). Planificado: Cloudflare y el proxy de CoolifySin TLS (HTTP)

Servidor de producción

La Academia corre en un contenedor LXC de Proxmox (172246) del servidor de la empresa, con Docker y Coolify instalados dentro del contenedor. Hoy se usa desde la red interna de la empresa, en http://192.168.0.192. El sistema operativo y los recursos asignados a ese contenedor: Por confirmar con TI.

Las notas de arquitectura del repositorio (CLAUDE.md, README.md) planificaban una máquina virtual con Ubuntu 22.04 LTS, 16 GB de RAM, 4+4 vCPU, 100 GB o más de disco y enlace de 100 Mbps. Lo real es un contenedor LXC (ver Discrepancias y puntos por confirmar).

Contenedor nuevo (mudanza prevista)

TI preparó un segundo contenedor al que se planea mudar la Academia, como prueba de recuperación con datos reales:

DatoValor
ContenedorLXC de Proxmox 12030
SistemaUbuntu
Recursos4 CPU, 8 GB de RAM, 4 GB de swap, 250 GB de disco
Opcionesnesting=1
Dirección IP192.168.0.191

El procedimiento está en Recuperación.

Lo que el host aporta es deliberadamente poco (principio "Docker primero, host al final" de docs/roadmap_v4.md):

  • Kernel y Docker Engine.
  • Coolify, que trae su propio Traefik.
  • El cron del sistema que lanza el respaldo diario (/etc/cron.d/delta-lms-backup, instalado por setup-backup.sh).
  • La carpeta de respaldos /var/backups/delta-lms y el registro /var/log/delta-lms-backup.log.
  • Los scripts de respaldo en /opt/delta-lms, copiados desde la imagen de Moodle (el repositorio es privado y Coolify no los deja en el host).

Endurecimiento del host

La documentación de arquitectura prevé un endurecimiento base CIS v8 y, para cuando la Academia se publique con Cloudflare, un firewall ufw que solo admita las IP de Cloudflare. En el repositorio no hay un script de endurecimiento (el hardening-cis8.sh que menciona la estructura sugerida no existe). Estado del endurecimiento y del firewall en producción: Por confirmar con TI.

Coolify

Coolify es la plataforma de despliegue en producción:

  1. Toma el repositorio de GitHub como fuente y construye la imagen con infrastructure/moodle/Dockerfile.
  2. Inyecta las variables de entorno definidas en su interfaz (contraseñas, claves de IA, SMTP).
  3. Levanta los servicios de docker-compose.yml (mariadb, moodle, moodle-cron).
  4. Genera automáticamente la ruta de Traefik a partir de la configuración de Domains de la aplicación. Por eso el servicio moodle no lleva etiquetas de Traefik en docker-compose.yml: si se dejaran, Traefik vería dos servicios para el mismo router y respondería 502.
  5. Muestra el estado de salud de cada servicio usando los healthchecks del compose.

Coolify puede renombrar contenedores y volúmenes con un identificador propio (por ejemplo moodle-<uuid> o <uuid>_moodledata). Los scripts de respaldo y restauración localizan los contenedores por la etiqueta com.docker.compose.service, así que no dependen de esos nombres.

Modo sin Coolify (recuperación de desastres)

docker-compose.yml incluye un servicio traefik (imagen traefik:v3) bajo el perfil standalone, que solo se levanta con docker compose --profile standalone up -d. Usa:

  • infrastructure/traefik/traefik.yml: puertos 80 y 443, redirección de HTTP a HTTPS, proveedor Docker sin exposición por defecto, panel desactivado y registro de accesos.
  • infrastructure/traefik/dynamic/moodle.yml: middlewares de cabeceras y límite de peticiones (ver Seguridad).

En ese modo hay que reactivar las etiquetas de Traefik que están comentadas en el servicio moodle. La configuración estática de traefik.yml no define resolvedor de certificados; cómo se obtiene el certificado TLS en este modo es Por confirmar.

Cloudflare y dominio (planificado)

Hoy la Academia no está publicada en Internet: se entra desde la red interna de la empresa en http://192.168.0.192, por HTTP, y Moodle queda con sslproxy = false. Lo planificado para la publicación:

  • Dominio público: learning.deltamontero.net, con el proxy de Cloudflare activado apuntando al servidor.
  • Cloudflare terminará la conexión HTTPS del usuario y reenviará al servidor; el proxy de Coolify (Traefik) entrega la petición a Moodle por HTTP en el puerto 80.
  • Moodle se configura con sslproxy = true cuando MOODLE_WWWROOT empieza por https://, para generar siempre direcciones HTTPS aunque reciba HTTP internamente.
  • La IP real del usuario se toma de X-Forwarded-For ignorando las redes internas de Docker y los rangos de Cloudflare (ajuste reverseproxyignore, ya activo en el código).

MOODLE_WWWROOT debe coincidir exactamente con la dirección que ve el usuario. Una diferencia (otro dominio, http en lugar de https, una barra final) provoca bucles de redirección y fallos de tokens y sesiones.

Modo SSL de Cloudflare y origen del certificado del servidor (Let's Encrypt por Traefik según README.md) para la publicación con dominio: Por confirmar con TI.

Recursos

Límites de memoria definidos en docker-compose.yml:

Serviciomem_limitNotas
mariadb2 GBinnodb-buffer-pool-size=1G
moodle3 GBPHP con memory_limit=512M por proceso
moodle-cron1 GB
Total6 GBSin contar Coolify, Traefik ni el sistema

La documentación de la Fase 5 advierte que Dify levanta entre 8 y 10 contenedores adicionales; con la memoria del servidor (el plan hablaba de 16 GB; el contenedor nuevo 12030 tiene 8 GB) habría que medir antes de desplegarlo (ver Inteligencia artificial).

Salida a Internet que necesita el servidor

DestinoPuertoPara quéQuién lo usa
smtp.office365.com587 (STARTTLS)Correos de la Academiamoodle, moodle-cron
api.groq.com443Chat de IA y agente de accionesmoodle
www.youtube.com443Revisión de disponibilidad de videos (oEmbed) en la revisión de módulosmoodle
Rangos de Cloudflare (planificado)443Tráfico entrante de usuarios cuando se publique con dominio; hoy los usuarios entran desde la red internaProxy de Coolify
Destino de la copia externa (rclone)443Copia del respaldo fuera del servidorHost (pendiente de configurar)

La imagen prefiere IPv4 (/etc/gai.conf): smtp.office365.com también publica direcciones IPv6 y, si el servidor no tiene salida IPv6, el envío fallaba con "Network is unreachable".

Reloj y zona horaria

La plataforma trabaja en hora de Ecuador, pero el servidor está en UTC. Conviene tener presente qué usa cada parte:

ComponenteZona horariaDónde se define
PHP (Apache y CLI)America/Guayaquilzz-delta.ini en la imagen
Moodle (fechas que ve el usuario)America/Guayaquiltimezone en moodle-config.sh
Formato de fecha%d de %B de %Ydateformat en moodle-config.sh
Sistema operativo de los contenedoresNo se define TZ (predeterminado de la imagen; Por confirmar: UTC)
Host de producciónUTCRegistro de pruebas de docs/drp.md
Respaldo diario2:00 de Ecuador = 7:00 UTCsetup-backup.sh convierte la hora sola (Ecuador es UTC-5 sin horario de verano)

El 2026-10-06 el reloj del servidor estaba unos 16 minutos adelantado y sin sincronizar (NTP service: inactive). Afecta a los códigos de verificación en dos pasos (MFA) de los super administradores y a la hora de los registros. La corrección (NTP en el nodo Proxmox) está pendiente en TI.

Entorno local de desarrollo

docker-compose.override.yml se carga automáticamente con docker compose y:

  • Publica el puerto 8080:80 del contenedor moodle.
  • Fija MOODLE_WWWROOT=http://localhost:8080 en moodle y moodle-cron (con HTTP, sslproxy queda en false).
  • Vacía las etiquetas de Traefik.

El primer arranque instala Moodle y tarda varios minutos; los siguientes son rápidos porque la base de datos ya existe. Para un buzón de pruebas local, .env.example sugiere un capturador de correo como Mailpit o el desvío MOODLE_MAIL_DIVERT_TO.

Comandos de uso diario: Entorno local. Despliegue en el servidor: Despliegue en producción.

En esta página