Watchtower auto-updates running containers when a new image is pushed to the registry. The original containrrr/watchtower is effectively unmaintained — nickfedor/watchtower is the active fork, and what I deploy everywhere now.

Two deploys below: one-line docker run for ad-hoc hosts, and a docker-compose.yml for anything that lives long enough to belong in a compose file.

One-line `docker run`

Polls every 5 minutes (-i 300) and removes the old image after a successful pull (--cleanup):

docker run -d \
  --restart always \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  nickfedor/watchtower:latest \
  -i 300 --cleanup

That's the whole thing. Everything below is iterations on the same shape — same image, same socket mount, different flags or scoping.

Docker Compose

Drop into docker-compose.yml and docker compose up -d:

services:
  watchtower:
    image: nickfedor/watchtower:latest
    container_name: watchtower
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --interval 300 --cleanup

Long-form flags (--interval, --cleanup) read better in a compose file than the short forms. Same behaviour as the docker run above.

Same Compose file, with a few flags I actually use

Most of my hosts run something close to this:

services:
  watchtower:
    image: nickfedor/watchtower:latest
    container_name: watchtower
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      TZ: America/Los_Angeles
      WATCHTOWER_POLL_INTERVAL: 300
      WATCHTOWER_CLEANUP: "true"
      WATCHTOWER_INCLUDE_RESTARTING: "true"
      WATCHTOWER_LABEL_ENABLE: "true"
      WATCHTOWER_ROLLING_RESTART: "true"
      WATCHTOWER_NOTIFICATIONS: shoutrrr
      WATCHTOWER_NOTIFICATION_URL: "discord://token@channel"

Why each of these:

  • WATCHTOWER_LABEL_ENABLE=true — opt-in mode. Watchtower only touches containers with com.centurylinklabs.watchtower.enable=true. Safer default than letting it manage everything on the host.
  • WATCHTOWER_ROLLING_RESTART=true — restarts containers one at a time instead of stopping all updateable containers, pulling, and starting them back. Avoids a brief "everything is down" window.
  • WATCHTOWER_CLEANUP=true — equivalent to --cleanup; removes the dangling old image after a successful update.
  • WATCHTOWER_NOTIFICATIONS=shoutrrr — pipes update events through shoutrrr; URL format covers Discord, Slack, Telegram, Pushover, generic webhook, etc.

Opt-in label on the containers you want updated

When WATCHTOWER_LABEL_ENABLE=true, every container that should be auto-updated needs the label:

services:
  myapp:
    image: ghcr.io/me/myapp:latest
    labels:
      com.centurylinklabs.watchtower.enable: "true"

Anything without the label is ignored. Pin the image tag explicitly when you don't want auto-updates (e.g. postgres:16.4, not postgres:latest) — Watchtower only updates when the digest behind a tag changes, so a pinned tag is naturally pinned in place.

Run-once mode (useful for cron / CI)

If you'd rather drive updates from an external scheduler than have a long-running watcher, run Watchtower one-shot:

docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  nickfedor/watchtower:latest \
  --run-once --cleanup

Pair with a systemd timer or cron job. Useful when you want updates to happen at a specific time (3 AM maintenance window) instead of on a polling interval.

Worth knowing

  • Mounting the Docker socket is root-on-host. Anything inside the Watchtower container can control Docker, which is equivalent to root on the host. Don't run untrusted images this way, and don't expose the socket over TCP.
  • Image digest, not tag, is what triggers an update. If you docker pull myimage:latest and the digest is unchanged, Watchtower does nothing. This is the right behaviour but trips people up when they expect a "force restart" from :latest.
  • --cleanup only removes the old image that the container was using before the update. It doesn't garbage-collect unrelated dangling images — use docker image prune for that.
  • Self-update works. Watchtower will update its own container; it shuts itself down, the new container takes over with the same config. No special handling needed.
  • Don't run two Watchtowers against the same containers. They'll race each other restarting things. One per host (or use label scoping if you genuinely need multiple instances watching different sets).

References