How to Auto-Update Docker Containers with Watchtower (and When Not To)

Unattended-upgrades keeps the operating system patched (tutorial 36), but nothing updates your Docker containers unless you run docker compose pull. Watchtower is a container that checks the others for new images on a schedule, pulls them, and restarts each one with exactly the settings it had. Set it to run at 4 am on Sundays, tell it to leave the fragile apps alone, and have it message you about what it did.
Quick reference
| Task | What |
|---|---|
| Folder | ~/docker/watchtower/compose.yaml |
| Schedule | WATCHTOWER_SCHEDULE in cron format (six fields, with seconds) |
| Exclude a container | label com.centurylinklabs.watchtower.enable=false |
| Notify only, don't update | WATCHTOWER_MONITOR_ONLY=true |
| Clean old images | WATCHTOWER_CLEANUP=true |
| Run once now | docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --run-once |
1. The compose file
mkdir -p ~/docker/watchtower && cd ~/docker/watchtower
nano compose.yaml
services:
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
environment:
TZ: Europe/London
WATCHTOWER_SCHEDULE: "0 0 4 * * 0"
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_INCLUDE_STOPPED: "false"
WATCHTOWER_NOTIFICATION_URL: "telegram://BOT_TOKEN@telegram?chats=CHAT_ID"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
- The schedule has six fields (seconds first): 0 0 4 * * 0 is 04:00 every Sunday.
- CLEANUP deletes the old image after a successful update, which is what stops the disk filling.
- The notification URL uses the Shoutrrr format: Telegram, Discord, email (smtp://user:pass@host:587/?from=a@b&to=c@d), Pushover, Slack and more are all supported.
docker compose up -d
2. Exclude what shouldn't auto-update
Some apps need you to read release notes before updating — databases, Immich, Nextcloud, Home Assistant. Add a label to those services in their compose files:
labels:
- com.centurylinklabs.watchtower.enable=false
and docker compose up -d there. Watchtower skips them and tells you an update exists instead.
Prefer the other way round? Set WATCHTOWER_LABEL_ENABLE=true and only containers labelled enable=true are touched.
3. Or notify only
If automatic restarts make you nervous, add:
WATCHTOWER_MONITOR_ONLY: "true"
Watchtower then just messages you "new image for jellyfin" and you update by hand. Many people run it this way.
4. Pinned versions still work
A container on nextcloud:30 only gets 30.x updates — Watchtower pulls whatever tag the container was started with. Pin major versions for things with migration steps and leave latest for the simple ones.
What Watchtower can't do
It doesn't rebuild images you built yourself, and it doesn't read compose.yaml changes — it recreates the container as it was. If you change the compose file, run docker compose up -d yourself.
Troubleshooting
Nothing ever updates. Check the schedule has six fields and the time zone is set. docker logs watchtower shows "Scheduling first run".
An app broke after an update. Roll back by pinning the previous tag in its compose file (image: app:1.2.3) and up -d. Then add the exclude label.
Notifications don't arrive. Test the URL with docker run --rm containrrr/watchtower --notification-url "..." --run-once.
Watchtower updated itself and vanished. Normal — it restarts. If it doesn't, docker compose up -d in its folder.
Wrapping up
One container, a Sunday-morning schedule, cleanup on, a Telegram message with the results. Label the fragile apps so they wait for you. Between this and unattended-upgrades, the server keeps itself current and you keep the decisions that matter.
