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
| Elemento | Producción | Desarrollo local |
|---|---|---|
| Host | Contenedor LXC de Proxmox 172246 de la empresa, con Docker y Coolify dentro | Equipo del desarrollador con Docker |
| Orquestación | Coolify (con su propio Traefik) | docker compose |
| Dirección | Hoy http://192.168.0.192 (red interna). Planificado: https://learning.deltamontero.net | http://localhost:8080 |
| Archivos de compose | docker-compose.yml | docker-compose.yml + docker-compose.override.yml (se carga solo) |
| Variables de entorno | Coolify → Environment Variables | Archivo .env (no se sube al repositorio) |
| TLS | Hoy sin TLS (HTTP en la red interna). Planificado: Cloudflare y el proxy de Coolify | Sin 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:
| Dato | Valor |
|---|---|
| Contenedor | LXC de Proxmox 12030 |
| Sistema | Ubuntu |
| Recursos | 4 CPU, 8 GB de RAM, 4 GB de swap, 250 GB de disco |
| Opciones | nesting=1 |
| Dirección IP | 192.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
crondel sistema que lanza el respaldo diario (/etc/cron.d/delta-lms-backup, instalado porsetup-backup.sh). - La carpeta de respaldos
/var/backups/delta-lmsy 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:
- Toma el repositorio de GitHub como fuente y construye la imagen con
infrastructure/moodle/Dockerfile. - Inyecta las variables de entorno definidas en su interfaz (contraseñas, claves de IA, SMTP).
- Levanta los servicios de
docker-compose.yml(mariadb,moodle,moodle-cron). - Genera automáticamente la ruta de Traefik a partir de la configuración de Domains de la aplicación. Por eso el servicio
moodleno lleva etiquetas de Traefik endocker-compose.yml: si se dejaran, Traefik vería dos servicios para el mismo router y respondería 502. - 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 = truecuandoMOODLE_WWWROOTempieza porhttps://, para generar siempre direcciones HTTPS aunque reciba HTTP internamente. - La IP real del usuario se toma de
X-Forwarded-Forignorando las redes internas de Docker y los rangos de Cloudflare (ajustereverseproxyignore, 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:
| Servicio | mem_limit | Notas |
|---|---|---|
mariadb | 2 GB | innodb-buffer-pool-size=1G |
moodle | 3 GB | PHP con memory_limit=512M por proceso |
moodle-cron | 1 GB | |
| Total | 6 GB | Sin 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
| Destino | Puerto | Para qué | Quién lo usa |
|---|---|---|---|
smtp.office365.com | 587 (STARTTLS) | Correos de la Academia | moodle, moodle-cron |
api.groq.com | 443 | Chat de IA y agente de acciones | moodle |
www.youtube.com | 443 | Revisión de disponibilidad de videos (oEmbed) en la revisión de módulos | moodle |
| Rangos de Cloudflare (planificado) | 443 | Tráfico entrante de usuarios cuando se publique con dominio; hoy los usuarios entran desde la red interna | Proxy de Coolify |
| Destino de la copia externa (rclone) | 443 | Copia del respaldo fuera del servidor | Host (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:
| Componente | Zona horaria | Dónde se define |
|---|---|---|
| PHP (Apache y CLI) | America/Guayaquil | zz-delta.ini en la imagen |
| Moodle (fechas que ve el usuario) | America/Guayaquil | timezone en moodle-config.sh |
| Formato de fecha | %d de %B de %Y | dateformat en moodle-config.sh |
| Sistema operativo de los contenedores | No se define TZ (predeterminado de la imagen; Por confirmar: UTC) | |
| Host de producción | UTC | Registro de pruebas de docs/drp.md |
| Respaldo diario | 2:00 de Ecuador = 7:00 UTC | setup-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:80del contenedormoodle. - Fija
MOODLE_WWWROOT=http://localhost:8080enmoodleymoodle-cron(con HTTP,sslproxyqueda enfalse). - 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.