Problema
En entornos homelab o pequeñas infraestructuras se combina a menudo Proxmox como hipervisor, LXC para aislar servicios y Docker Compose para orquestar contenedores. Cuando se usa Renovate para mantener actualizadas las imágenes, el flujo típico es:
- Renovate detecta una nueva versión.
- Crea un Pull Request (PR) que actualiza el archivo
docker-compose.yml. - El administrador revisa y fusiona el PR.
El punto crítico aparece después del merge: los cambios deben llegar a cada LXC que ejecuta Docker, y el contenedor debe ser recreado con la nueva imagen. Hacer esto manualmente (login en cada LXC, git pull, docker compose up -d) es tedioso y propenso a errores, sobre todo cuando hay varios hosts. Necesitamos un mecanismo que:
- Detecte el merge en GitHub.
- Propague el archivo actualizado a los LXC correspondientes.
- Ejecute los comandos de Docker de forma automática.
- Mantenga GitHub como única fuente de verdad.
- Permita limpiar imágenes obsoletas.
Causa
Los fallos habituales en este tipo de flujo provienen de tres áreas:
- Ausencia de orquestación post‑merge – GitHub no tiene por defecto un agente que notifique a los hosts. Sin un webhook o CI, los LXC nunca saben que el archivo cambió.
- Desalineación de credenciales – Cada LXC necesita acceso SSH sin contraseña al repositorio y al servidor de CI. Configuraciones incompletas provocan fallos de autenticación.
- Inconsistencias de entorno – Si el
docker-compose.ymldepende de variables de entorno locales, la ejecución remota puede fallar al no encontrar esas variables.
Solución
Un enfoque reusable combina Renovate, GitHub Actions y SSH para ejecutar docker compose en los LXC justo después del merge. Los pasos clave son:
- Repositorio único – Mantén todos los
docker-compose.ymlen un solo repo. Cada LXC tiene su propia carpeta (media/,adguard/,proxy/, …) para evitar colisiones. - Clave SSH compartida – Genera una clave RSA dedicada al CI, añádela al archivo
authorized_keysde cada LXC y como secretDEPLOY_KEYen GitHub. Así el workflow puede conectarse sin interacción. - Workflow de GitHub Actions – Un job que se dispara con
on: pusha la ramamain(omaster). El job:- Detecta los archivos modificados con
git diff --name-only ${{ github.event.before }} ${{ github.sha }}. - Por cada carpeta afectada, ejecuta un bloque SSH que:
cdal directorio del proyecto dentro del LXC.git pullpara sincronizar el compose.docker compose pullpara descargar la nueva imagen.docker compose up -d --remove-orphanspara recrear los contenedores.docker image prune -fpara limpiar imágenes no usadas.
- Detecta los archivos modificados con
- Manejo de variables – Usa un archivo
.envversionado o almacenado como secret en GitHub y copiado al LXC mediantescpantes de ejecutardocker compose.
Ejemplo de workflow
name: Deploy Docker Compose updates
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect changed services
id: changes
run: |
git diff --name-only ${{ github.event.before }} ${{ github.sha }} |
grep '/docker-compose.yml$' |
cut -d'/' -f1 |
sort -u > services.txt
echo "services=$(cat services.txt | tr '\n' ' ')" >> $GITHUB_OUTPUT
- name: Deploy to each LXC
if: steps.changes.outputs.services != ''
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
while read service; do
echo "Deploying $service"
ssh -i <(echo "$DEPLOY_KEY") -o StrictHostKeyChecking=no root@${service}.lxc <<'EOF'
cd /opt/${service}
git pull
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
EOF
done <<< "${{ steps.changes.outputs.services }}"
Detalles importantes
- Naming convention: Cada LXC debe resolverse mediante DNS interno (
media.lxc,adguard.lxc, …) o IP estática. El workflow usa${service}.lxcpara construir el host. - Privilegios: El usuario
rootsimplifica la ejecución de Docker, pero se puede crear un usuario con membresía en el grupodockery usarsudo. - Idempotencia:
docker compose up -d --remove-orphansgarantiza que los contenedores que ya no aparecen en el compose se eliminen automáticamente.
Cuándo aplicar esta solución
- Múltiples hosts LXC que ejecutan Docker y comparten el mismo repositorio de compose.
- Renovate o cualquier herramienta que genere PRs y requiere aprobación manual.
- Entorno controlado donde los LXC están en la misma red y pueden ser alcanzados por SSH desde los runners de GitHub.
- No es adecuada si:
- Los contenedores se ejecutan en un clúster Kubernetes (K8s tiene su propio mecanismo de despliegue).
- Los LXC están aislados detrás de firewalls sin acceso SSH desde la nube.
- Se necesita un control de versiones de base de datos o migraciones complejas que requieran pasos adicionales.
Código
# Generar clave para CI (ejecutar una sola vez)
ssh-keygen -t rsa -b 4096 -C "github-actions-deploy" -f github_deploy_key -N ""
# Copiar clave pública a cada LXC
for host in media.lxc adguard.lxc proxy.lxc; do
ssh-copy-id -i github_deploy_key.pub root@$host
done
# Añadir la clave privada como secret en GitHub (DEPLOY_KEY)
# No subir nunca el archivo a ningún repositorio.
Verificación
- Prueba de conectividad – Desde el runner de GitHub (puedes usar
actlocalmente) ejecutassh -i github_deploy_key [email protected] echo ok. Debería responderok. - Simular cambio – Modifica manualmente un
docker-compose.ymlen una carpeta, haz commit y push amain. Observa el log del workflow; debe listar la carpeta como cambiada y ejecutar los comandos remotos. - Validar contenedor – En el LXC, ejecuta
docker psy verifica que la etiqueta de la imagen corresponde a la versión esperada. - Limpiar imágenes – Ejecuta
docker image ls -aantes y después del despliegue; la lista de<none>debería reducirse.
Notas adicionales
- Gestión de secretos: Si cada servicio necesita variables distintas, crea secrets con prefijo (
MEDIA_DB_PASS,ADGUARD_API_KEY) y pásalos al workflow conenv:. Dentro del bloque SSH, escribe los valores en un.envtemporal antes de lanzardocker compose. - Rollback rápido – Si la actualización rompe algo, revertir el PR y volver a ejecutar el workflow restaura la versión anterior sin tocar los LXC manualmente.
- Escalabilidad – Para más de diez LXC, considera agruparlos por zona y ejecutar despliegues en paralelo usando matrix en GitHub Actions.
- Auditoría – Cada ejecución del workflow queda registrada en GitHub Actions, proporcionando un historial claro de quién aprobó y cuándo se desplegó cada cambio.
Con este patrón, la cadena de confianza se mantiene en GitHub, Renovate sigue proporcionando la visibilidad de actualizaciones y el proceso de despliegue se vuelve completamente automático una vez que el PR es mergeado. La solución es ligera, reutilizable y se adapta a la mayoría de los homelabs que combinan Proxmox, LXC y Docker Compose.