Watchtower keeps your running Docker containers up to date: it watches for new images, pulls them, and recreates the container β then deletes the old image. Itβs the quiet janitor behind a self-hosted stack.
This is the piece that acts on every other guide in Tools β each one carries a
com.centurylinklabs.watchtower.enable=truelabel. Only labelled containers get touched (with--label-enable), so Watchtower updates exactly what you opt in.
The original is archived β use a maintained fork
containrrr/watchtowerwas archived on 2025-12-17 (read-only now, ~24k β). The actively-maintained continuation is the Docker imagenickfedor/watchtower(used below) β its source lives atnicholas-fedor/watchtoweron GitHub (donβt be thrown by the name mismatch; itβs still shipping releases). Itβs a drop-in replacement β same labels, same flags.
Tag discipline (the rule that keeps auto-updates safe)
Watchtower updates a container to the newest image for the tag you pinned. The tag is your blast radius:
postgres:14β updates to14.1,14.2, β¦ (minor/patch) but not15. β Safe.postgres:latestβ updates across major versions too (14β15β16). β οΈ A major bump can break your app or need a migration β at 4am, unattended.
Pin to a major-version tag wherever the project versions cleanly. A note on
the other Tools guides: several use :latest (Vaultwarden, Sshwifty)
for convenience β fine while youβre reading release notes yourself, but under
auto-update that means Watchtower will happily pull a breaking major. Either pin
them, or put them in monitor-only (below).
Daily auto-updates β docker-compose.yml
# docker-compose file for watchtower (auto-updates labelled containers)
# start: docker compose up -d
name: watchtower-localhost
services:
watchtower:
image: nickfedor/watchtower
container_name: watchtower.localhost
restart: always
mem_limit: 32M
volumes:
- /var/run/docker.sock:/var/run/docker.sock # gives it control of the daemon
- /etc/localtime:/etc/localtime:ro # so the schedule uses local time
labels:
- com.centurylinklabs.watchtower.enable=true # update watchtower itself too
# Update labelled containers at 04:15, clean up old images afterward.
# (schedule is 6-field cron: sec min hour dom mon dow)
command: --label-enable --cleanup --schedule "0 15 4 * * *"The docker.sock mount is root-equivalent
Anything that can talk to
/var/run/docker.sockeffectively has root on the host. Watchtower needs it β so keep this container minimal, pinned, and donβt add anything else to it.
Update manually β run.sh
#!/bin/sh
# Run watchtower once against all labelled containers, then exit.
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
nickfedor/watchtower --cleanup --label-enable --run-onceSuggested improvements
1. Turn on notifications β this is the big one. Auto-updates that silently break a service at 4am are the nightmare. Notifications tell you what updated so you can check it. Watchtower uses shoutrrr URLs β ntfy, Telegram, Discord, Slack, email:
environment:
- WATCHTOWER_NOTIFICATIONS=shoutrrr
- WATCHTOWER_NOTIFICATION_URL=ntfy://ntfy.sh/my-watchtower-topic
- WATCHTOWER_NOTIFICATION_REPORT=true # only notify when something changed2. Monitor-only for anything you canβt afford to break blindly. For a password manager or a database, notify but donβt auto-update β then you update by hand after reading the release notes. Per-container, no global change:
labels:
- com.centurylinklabs.watchtower.enable=true
- com.centurylinklabs.watchtower.monitor-only=true # check + notify, don't update3. Rolling restart (--rolling-restart) updates one container at a time
instead of all at once β gentler on a busy host.
4. Ubuntu + AppArmor gotcha (you hit this): AppArmor can stop Watchtower from
shutting containers down. Fix with sudo aa-remove-unknown, then verify with a
manual ./run.sh --run-once.
5. Watch the log after the first scheduled run β docker logs watchtower.localhost
confirms itβs finding your labelled containers and not erroring on the socket.