Ir al contenido
  1. Posts/

Watchtower: actualiza tus containers automáticamente (y olvídate del 'docker pull')

Tabla de contenido

Soy de esas personas que tiene 40 containers corriendo en producción y cada vez que veo un “new image available” me da pereza actualizar. No porque sea difícil, sino porque son 40. Y algunos necesitan recrear el stack completo. Y otros dependen de otros. Y siempre hay uno que se rompe al actualizar.

Watchtower resolvió este problema por mi. Ahora mis containers se actualizan solos, de noche, sin que yo toque nada. Y cuando algo se rompe (porque siempre se rompe algo), tengo rollback automático.

Te cuento cómo lo monté, qué errores cometí y cómo configurarlo para que no te despiertes con el homelab roto.

Qué es Watchtower y por qué lo uso
#

Watchtower es un container que vigila tus otros containers. Cada X horas (tú decides), comprueba si hay nuevas imágenes disponibles en Docker Hub o tu registry privado. Si las hay, hace pull, para el container viejo, levanta el nuevo con la misma configuración, y borra la imagen antigua.

Todo automático. Sin tocar nada.

Lo uso porque:

  1. Me ahorro trabajo manual - Antes dedicaba 30 minutos cada domingo a actualizar todo. Ahora dedico 0.
  2. Seguridad - Los parches de seguridad se aplican automáticamente. No tengo que acordarme.
  3. Consistencia - Todos mis servidores tienen la misma lógica de actualización. No hay “este lo actualicé la semana pasada” vs “este lleva 3 meses sin tocar”.
  4. Rollback automático - Si el nuevo container no arranca, Watchtower vuelve a la imagen anterior. Me ha salvado varias veces.

La primera vez que lo probé fue en un servidor de pruebas con 5 containers. Funcionó tan bien que ahora lo tengo en todos mis nodos. Incluido producción.

Instalación básica (la que todos usan)
#

La mayoría de guías te dicen que ejecutes esto:

1
2
3
4
docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower

Y funciona. Pero es una mala idea.

Por qué? Porque va a actualizar TODOS tus containers. Incluidos los que no quieres que se actualicen (bases de datos, por ejemplo). Y lo va a hacer cada 24 horas, sin preguntarte.

Yo lo hice así la primera vez. A las 3 de la mañana se actualizó mi PostgreSQL y rompió la compatibilidad con n8n. Me desperté sin automatizaciones funcionando. No fue divertido.

Mi configuración real (con docker-compose)
#

Así es como lo tengo montado ahora:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
version: "3.8"

services:
  watchtower:
    container_name: watchtower
    image: containrrr/watchtower:latest
    restart: unless-stopped
    environment:
      # Horario de escaneo: todos los días a las 4:00 AM
      - WATCHTOWER_SCHEDULE=0 0 4 * * *
      
      # Solo actualizar containers con label específico
      - WATCHTOWER_LABEL_ENABLE=true
      
      # Limpiar imágenes antiguas
      - WATCHTOWER_CLEANUP=true
      
      # Incluir containers detenidos
      - WATCHTOWER_INCLUDE_STOPPED=false
      
      # Notificaciones por webhook a Discord
      - WATCHTOWER_NOTIFICATIONS=shoutrrr
      - WATCHTOWER_NOTIFICATION_URL=discord://token@id
      
      # Rollback si falla el health check
      - WATCHTOWER_ROLLING_RESTART=true
      
      # Timeout para health check (segundos)
      - WATCHTOWER_TIMEOUT=300
      
      # No actualizar en seco, esperar al siguiente ciclo
      - WATCHTOWER_RUN_ONCE=false
      
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

Punto por punto:

WATCHTOWER_SCHEDULE - Cron expression para definir cuándo se ejecuta. Yo lo tengo a las 4:00 AM porque es cuando menos tráfico hay en mi homelab. Si algo se rompe, lo veo por la mañana.

WATCHTOWER_LABEL_ENABLE - Esta es la clave. Solo actualiza containers que tengan el label com.centurylinklabs.watchtower.enable=true. Así controlo exactamente qué se actualiza.

WATCHTOWER_CLEANUP - Borra las imágenes antiguas después de actualizar. Si no, tu disco se llena de imágenes viejas.

WATCHTOWER_NOTIFICATIONS - Envío notificaciones a Discord cada vez que hay una actualización. Así sé qué cambió sin tener que revisar logs.

WATCHTOWER_ROLLING_RESTART - Si el nuevo container falla el health check, vuelve a la imagen anterior. Esto es oro puro.

Cómo marcar qué containers se actualizan
#

Este es el truco. En cada container que quiero que Watchtower actualice, añado este label:

1
2
3
4
5
6
services:
  immich:
    image: ghcr.io/immich-app/immich-server:release
    labels:
      - com.centurylinklabs.watchtower.enable=true
    # resto de configuración...

Y listo. Watchtower solo tocará ese container.

Los containers que NO tienen ese label (bases de datos, por ejemplo) quedan intactos. Yo los actualizo manualmente cuando sé que no voy a romper nada.

Mi regla personal:

  • Actualización automática: servicios stateless (Plex, Jellyfin, servicios web, reverse proxies)
  • Actualización manual: bases de datos, servicios con estado crítico (PostgreSQL, Redis, n8n)
  • Nunca actualizar: containers legacy que funcionan y no quiero tocar

Notificaciones (saber qué pasó sin mirar logs)
#

Watchtower puede enviar notificaciones a un montón de servicios. Yo uso Discord porque ya lo tengo abierto 24/7.

Configuración:

  1. Crea un webhook en tu servidor de Discord (Server Settings > Integrations > Webhooks)
  2. Copia la URL (algo como https://discord.com/api/webhooks/123456/token)
  3. Conviértela al formato Shoutrrr:
1
discord://token@id

Ejemplo real (datos falsos):

1
discord://ABCD1234efgh5678IJKL9012mnop3456qrst7890uvwx1234yzAB5678/123456789012345678

Se pone en la variable WATCHTOWER_NOTIFICATION_URL.

Ahora cada vez que Watchtower actualiza algo, recibes un mensaje así:

1
2
Watchtower: Updated immich-server from v1.94.1 to v1.95.0
Watchtower: Updated jellyfin from 10.8.13 to 10.9.0

Si algo falla:

1
Watchtower: Failed to update vaultwarden - container health check failed, rolled back to previous image

Tranquilidad mental.

Estrategias de actualización (cómo NO romper todo)
#

Después de usar Watchtower en producción durante 8 meses, estas son las estrategias que me funcionan:

1. Actualizar por oleadas
#

No actualices todo a la vez. Divide tus containers en grupos:

  • Oleada 1 (lunes 4 AM): servicios no críticos (Plex, Jellyfin, wikis)
  • Oleada 2 (miércoles 4 AM): servicios importantes pero con fallback (reverse proxies, dashboards)
  • Oleada 3 (nunca): bases de datos y servicios críticos (manual)

Cómo? Tengo 3 instancias de Watchtower con schedules diferentes y labels diferentes:

1
2
3
4
5
6
7
8
9
# watchtower-tier1.yml
environment:
  - WATCHTOWER_SCHEDULE=0 0 4 * * 1  # Lunes
  - WATCHTOWER_ENABLE_LABEL=watchtower.tier=1

# watchtower-tier2.yml
environment:
  - WATCHTOWER_SCHEDULE=0 0 4 * * 3  # Miércoles
  - WATCHTOWER_ENABLE_LABEL=watchtower.tier=2

Y en cada container:

1
2
labels:
  - watchtower.tier=1  # o tier=2

Así si la oleada 1 rompe algo, tengo 2 días para arreglarlo antes de que toque servicios críticos.

2. Health checks obligatorios
#

Watchtower solo puede hacer rollback si el container tiene health check. Si no lo tiene, asume que está bien aunque esté roto.

Ejemplo de health check para un servicio web:

1
2
3
4
5
6
7
8
9
services:
  immich:
    image: ghcr.io/immich-app/immich-server:release
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3001/api/server-info/ping"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

Si después de actualizar el health check falla 3 veces, Watchtower vuelve a la imagen anterior. Automático.

3. Monitoring post-actualización
#

Tengo un script que corre 10 minutos después de cada actualización de Watchtower. Comprueba que todos los servicios responden correctamente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/bin/bash
# check-services.sh

SERVICES=(
  "https://immich.midnightlab.build"
  "https://vault.midnightlab.build"
  "https://n8n.midnightlab.build"
)

for service in "${SERVICES[@]}"; do
  if ! curl -sf "$service" > /dev/null; then
    curl -X POST https://discord.com/api/webhooks/... \
      -H "Content-Type: application/json" \
      -d "{\"content\": \"⚠️ $service no responde después de actualizar\"}"
  fi
done

Lo ejecuto con cron 10 minutos después de la ventana de actualización:

10 4 * * * /home/user/scripts/check-services.sh

4. Registries privados (para containers custom)
#

Si tienes containers propios en un registry privado (Gitea, Harbor, etc), Watchtower también puede actualizarlos.

Necesitas pasar las credenciales:

1
2
3
environment:
  - REPO_USER=tu-usuario
  - REPO_PASS=tu-password

O mejor, usando Docker secrets:

1
2
3
4
5
6
7
secrets:
  - docker-registry-user
  - docker-registry-pass

environment:
  - REPO_USER_FILE=/run/secrets/docker-registry-user
  - REPO_PASS_FILE=/run/secrets/docker-registry-pass

Yo tengo varios containers propios en mi Gitea. Cada vez que hago push a main, GitHub Actions buildea la imagen, la sube al registry, y Watchtower la actualiza en producción. CI/CD casero.

Errores que cometí (para que no los cometas tú)
#

Error 1: Actualizar bases de datos sin snapshot
#

La primera vez que activé Watchtower dejé que actualizara mi PostgreSQL. La nueva versión cambió el formato de datos. Rollback imposible. Tuve que restaurar desde backup de hace 2 días.

Solución: Las bases de datos NUNCA van con Watchtower. Se actualizan manualmente después de hacer snapshot del volumen.

Error 2: No configurar health checks
#

Actualicé Vaultwarden. El nuevo container arrancó pero no respondía en el puerto esperado (cambió de 80 a 8080). Watchtower pensó que estaba bien porque el proceso arrancó. Pasé 2 horas sin acceso a mis contraseñas.

Solución: Health checks en TODOS los containers que actualiza Watchtower. Sin excepciones.

Error 3: Actualizar todo a la vez
#

Un lunes actualicé 40 containers simultáneamente. Traefik se actualizó, cambió el formato de configuración, y todos los demás servicios quedaron inaccesibles. Tardé 6 horas en arreglar todo.

Solución: Actualizar por oleadas. Empezar por lo menos crítico. Esperar 48h antes de tocar lo importante.

Error 4: No leer changelogs
#

Actualicé Immich automáticamente de v1.94 a v1.95. La nueva versión requería migración manual de base de datos. Watchtower no puede hacer eso. El servicio quedó roto hasta que ejecuté el script de migración.

Solución: Para servicios con datos críticos, añadir un paso manual. Watchtower actualiza la imagen, pero tengo un checklist en Notion de “cosas que revisar después de actualizar X”.

Error 5: Timeout demasiado corto
#

Configuré WATCHTOWER_TIMEOUT=30. Algunos de mis containers tardan más de 30 segundos en arrancar (Nextcloud, por ejemplo). Watchtower pensaba que habían fallado y hacía rollback innecesario.

Solución: WATCHTOWER_TIMEOUT=300 (5 minutos). Si un container tarda más de 5 minutos en arrancar, probablemente hay un problema real.

Configuración avanzada (solo si lo necesitas)
#

Actualizar solo tags específicos
#

Por defecto Watchtower actualiza a latest. Pero si quieres más control:

1
2
3
4
5
services:
  immich:
    image: ghcr.io/immich-app/immich-server:v1.95.0
    labels:
      - com.centurylinklabs.watchtower.enable=true

Watchtower solo actualizará si hay un nuevo v1.95.x. No saltará a v1.96.0 sin que tú cambies el tag explícitamente.

Yo uso esto para servicios críticos. Prefiero actualizar major versions manualmente.

Actualizar grupos de containers juntos
#

Algunos stacks necesitan actualizarse todos a la vez (Immich tiene server, machine-learning, web, etc).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
services:
  immich-server:
    labels:
      - com.centurylinklabs.watchtower.enable=true
      - com.centurylinklabs.watchtower.depends-on=immich-ml,immich-web
  
  immich-ml:
    labels:
      - com.centurylinklabs.watchtower.enable=true
  
  immich-web:
    labels:
      - com.centurylinklabs.watchtower.enable=true

Watchtower esperará a que todos estén disponibles antes de actualizar.

Dry run (ver qué se actualizaría sin actualizar)
#

Útil para testing:

1
2
3
4
5
6
docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower \
  --run-once \
  --cleanup \
  --debug

Te dice qué containers tienen actualizaciones disponibles pero no hace nada.

Alternativas a Watchtower
#

Watchtower no es la única opción. Otras que probé:

Diun - Solo notifica de nuevas imágenes, no actualiza. Bueno si quieres control total pero te da pereza revisar manualmente Docker Hub.

Ouroboros - Como Watchtower pero con menos features. Lo usé antes de Watchtower. Funciona bien pero el proyecto está menos activo.

Renovate - Para actualizar archivos docker-compose.yml en Git. Genial si usas GitOps. Yo lo uso en paralelo con Watchtower: Renovate propone cambios, yo los reviso, hago merge, Watchtower aplica.

Pulumi/Terraform - Overkill para homelab pero si ya usas IaC tiene sentido. Yo no llegué a ese nivel todavía.

Conclusión: automatiza lo aburrido
#

Actualizar containers manualmente es trabajo de mono. Es repetitivo, no aporta valor, y siempre se te olvida alguno.

Watchtower no es perfecto. Vas a tener que ajustar configuración. Te va a romper algo en algún momento. Pero incluso con esos problemas, me ahorra fácil 2 horas al mes.

Configuración que recomiendo para empezar:

  1. Instala Watchtower con WATCHTOWER_LABEL_ENABLE=true
  2. Añade el label solo a 2-3 containers no críticos
  3. Configura notificaciones a Discord/Telegram/donde sea
  4. Espera 1 semana
  5. Si no se rompió nada, añade más containers

En 2-3 semanas tendrás un sistema de actualización que funciona sin pensar en él.

Y cuando veas el mensaje “Watchtower: Updated 8 containers” a las 4 de la mañana mientras duermes, vas a sonreír.

Yo lo hice.