Problema

En muchos homelabs la lista de contenedores crece de forma orgánica: se añaden servicios copiando archivos docker‑compose.yml antiguos, se asignan puertos a mano y se crean volúmenes sin una política clara. Con el tiempo aparecen colisiones de puertos, variables de entorno olvidadas y health checks ausentes. Cada error obliga a volver a la máquina, revisar logs y, a menudo, a reiniciar manualmente los stacks. El patrón es claro: la configuración declarativa no está versionada ni validada antes de llegar al host, lo que genera inconsistencias y tiempo de inactividad evitables.

Causa

  1. Ausencia de GitOps – Los archivos de Compose viven solo en el servidor. Sin un repositorio que sirva como única fuente de verdad, cualquier cambio local se pierde o se duplica.
  2. Falta de validación estática – No hay linting ni pruebas de esquema que detecten puertos duplicados, variables sin valor o configuraciones inseguras antes del despliegue.
  3. Despliegues manuales – Ejecutar docker compose up -d directamente en el host evita cualquier control de calidad y dificulta la reversión.
  4. Gestión de secretos ad‑hoc – Variables sensibles se inyectan mediante archivos .env sin control de acceso, lo que aumenta el riesgo de exposición.
  5. Visibilidad limitada – Sin una herramienta que pueda inspeccionar contenedores en tiempo real, la detección de fallos depende de la intuición del administrador.

Solución

Implementar un flujo GitOps basado en Docker Compose, Gitea (o cualquier servidor Git con CI) y GitHub/Gitea Actions. El repositorio contiene:

  • Plantillas de Compose con anclajes (x-common-keys, x-common-logging) que garantizan opciones de seguridad, logging y restart policy.
  • Un archivo allocated-ports.md que registra cada puerto expuesto. Un script de CI compara el docker compose config renderizado con este inventario y falla si hay colisiones.
  • Un workflow de Actions que:
    1. Genera un .env temporal a partir de los secretos almacenados en Gitea.
    2. Ejecuta docker compose config para validar sintaxis y aplicar los anclajes.
    3. Corre Trivy contra Dockerfiles y contra imágenes resultantes.
    4. Lanza docker compose up -d --remove-orphans.
    5. Espera a que todos los health checks reporten healthy. Si alguno falla, captura los últimos logs y ejecuta una rollback basada en los IDs de imagen previos.
  • Un runner con acceso read‑write al socket Docker para despliegues y un proxy read‑only para herramientas de monitoreo.
  • Opcionalmente, un contenedor como OpenChamber que expone la CLI de Docker y Gitea a través de una UI web, permitiendo que un modelo de IA (por ejemplo, un LLM local) genere o corrija archivos YAML, inspeccione logs y dispare workflows desde cualquier dispositivo.

Paso a paso resumido

  1. Estructura del repositorio
    MyServer/
      docker/
        <stack>/
          docker-compose.yml
          Dockerfile
      config/
      .gitea/
        actions/
          deploy-stack/
            main.yml
    
  2. Plantilla de Compose – Incluye anclajes comunes y health checks obligatorios.
  3. Script de validación (dev validate) que ejecuta:
    • docker compose config
    • Linter de Compose (chequea no-new-privileges:true, restart, logging, healthcheck).
    • Comparación de puertos contra allocated-ports.md.
  4. Workflow de despliegue – Usa los pasos descritos arriba.
  5. Rollback automático – Guarda los IDs de imagen antes del docker compose up; si la fase de health check falla, re‑etiqueta y recrea los contenedores con esas imágenes.

Cuándo aplicar esta solución

  • Homelabs con más de 5 stacks donde la gestión manual ya genera colisiones de puertos o variables perdidas.
  • Entornos que requieren alta disponibilidad de servicios críticos (Home Assistant, DNS, reverse proxy).
  • Equipos que usan IA para generar o validar configuraciones; el flujo GitOps provee una barrera de seguridad antes de que la IA aplique cambios.
  • No es necesario si solo se ejecuta un único contenedor estático y no hay necesidad de versionado o rollback.

Código

# dev script – validación básica
#!/usr/bin/env bash
set -euo pipefail

# Render Compose y captura salida
rendered=$(docker compose -f "$1/docker-compose.yml" config)

# Chequeo de puertos duplicados
awk '/ports:/ {gsub(/[-"]/,"",$2); print $2}' <<<"$rendered" | \
  sort | uniq -d | if read dup; then
    echo "Puerto duplicado detectado: $dup"
    exit 1
  fi

# Linter mínimo (ejemplo)
if ! grep -q "no-new-privileges:true" <<<"$rendered"; then
  echo "Falta no-new-privileges en al menos un servicio"
  exit 1
fi

echo "Validación exitosa"

Verificación

  1. Push a main – Gitea ejecuta el workflow.
  2. Revisa la página de Actions – Debe pasar todas las etapas: render, lint, Trivy, despliegue, health check.
  3. Comprueba DNS – Si el workflow registra el dominio en AdGuard, verifica que dig <host>.home devuelve la IP esperada.
  4. Prueba de rollback – Induce un fallo (por ejemplo, cambia la etiqueta de la imagen a una inexistente) y confirma que los contenedores vuelven a la versión anterior y que los logs aparecen en la salida del job.

Notas adicionales

  • Límites de redes Docker – Cuando se usan muchos stacks, aumenta el default-address-pool en /etc/docker/daemon.json para evitar el error could not find an available, non-overlapping IPv4 address pool.
  • Separación de sockets – Usa docker-socket-proxy para limitar el acceso a docker.sock. Los runners de CI necesitan permisos de escritura; las herramientas de monitoreo pueden usar una instancia read‑only.
  • Gestión de secretos – Nunca almacenes valores sensibles en archivos .env versionados. Gitea Actions permite inyectar secretos directamente en el proceso de docker compose.
  • AI como asistente, no como autoridad – Deja que el modelo genere cambios, pero siempre pasa por el pipeline de validación antes de aplicar. Esto evita que un error de interpretación cause downtime.
  • Backup de volúmenes – Configura un contenedor como Duplicati o Restic que respalde ${DOCKERDIR} periódicamente. La restauración de datos es independiente del proceso de despliegue.