Watchtower: Auto-Update Your Docker Containers
Use Watchtower to automatically keep your Docker containers updated to the latest image versions, with notifications and scheduling options.
Every Docker container in your homelab is pinned to an image version that slowly goes stale. New releases add features, fix bugs, and patch security vulnerabilities. Keeping up with them manually means checking each project’s GitHub, pulling updated images, and recreating containers. That’s tedious enough that most people don’t do it consistently.
Watchtower automates this. It runs in the background, watches your containers, and pulls new image versions when they’re available. When it finds an update, it stops the old container, pulls the new image, and starts the container back up with the same configuration.
When Watchtower Makes Sense
Watchtower is a good fit for homelab containers running images tagged latest or other rolling tags. It’s less appropriate for production workloads where you want to control exactly when updates happen. for a homelab, the convenience usually outweighs the occasional surprise.
Containers where I use Watchtower: Uptime Kuma, Vaultwarden, Immich, Pi-hole, Nginx Proxy Manager. Anything where staying current matters and breaking changes in minor updates are rare.
Containers where I don’t: anything with a specific pinned version tag that I want to update deliberately (like the Postgres containers Immich uses), or anything where a bad update would be hard to recover from quickly.
Installation
mkdir -p /opt/watchtower
Create /opt/watchtower/docker-compose.yml:
services:
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_SCHEDULE=0 0 4 * * *
- WATCHTOWER_NOTIFICATIONS=shoutrrr
- WATCHTOWER_NOTIFICATION_URL=
restart: unless-stopped
/var/run/docker.sock: Watchtower needs access to the Docker socket to manage containers. This grants it significant access to the Docker daemon. it can stop, start, and pull containers. That’s required for it to do its job, but be aware of what you’re granting.
WATCHTOWER_CLEANUP=true: Removes the old image after updating. Without this, pulled-but-replaced images pile up and eat disk space.
WATCHTOWER_SCHEDULE: Cron expression for when Watchtower runs. 0 0 4 * * * means 4:00 AM every day. Running at night means updates don’t interrupt active use.
Notification Setup
Watchtower uses Shoutrrr for notifications, a single URL-based notification format that supports Telegram, Slack, Discord, email, and others.
Telegram:
telegram://YOUR_BOT_TOKEN@telegram?chats=YOUR_CHAT_ID
Replace YOUR_BOT_TOKEN with your Telegram bot token and YOUR_CHAT_ID with your chat ID. (See the Uptime Kuma guide for how to create a Telegram bot if you haven’t done that yet.)
Discord:
discord://TOKEN@CHANNEL_ID
Slack:
slack://HOOK_URL
Set your notification URL in the compose file:
- WATCHTOWER_NOTIFICATION_URL=telegram://YOUR_BOT_TOKEN@telegram?chats=YOUR_CHAT_ID
With notifications enabled, you’ll get a message whenever Watchtower updates a container: what was updated, what version it moved to, and whether it succeeded.
Start Watchtower
cd /opt/watchtower
docker compose up -d
Watchtower starts monitoring immediately. It runs its first check according to the schedule you set.
Running a Manual Update Check
If you want Watchtower to check and update right now instead of waiting for the next scheduled run:
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
--run-once
This runs a one-off check and exits. Useful for testing that notifications work or for getting updates applied immediately after a new release drops.
Excluding Containers from Updates
Watchtower monitors all running containers by default. To exclude specific containers, add this label to them in their compose file:
labels:
- "com.centurylinklabs.watchtower.enable=false"
Or, run Watchtower in opt-in mode where only labeled containers get updated:
environment:
- WATCHTOWER_LABEL_ENABLE=true
Then add this label to containers you want updated:
labels:
- "com.centurylinklabs.watchtower.enable=true"
I use the default (all containers) with explicit opt-out for containers I want to control manually. The opt-out label on a few Postgres containers is enough.
Monitoring Mode (No Auto-Updates)
If you want Watchtower to tell you when updates are available without actually applying them:
environment:
- WATCHTOWER_MONITOR_ONLY=true
This sends you notifications about available updates but doesn’t touch the containers. You apply updates yourself on your own schedule. Useful if you’re moving toward more deliberate update management but still want to know when things are outdated.
What Happens During an Update
When Watchtower finds a newer image:
- Pulls the new image
- Stops the running container
- Starts a new container from the new image using the same configuration (volumes, environment variables, ports, restart policy)
- Removes the old container
- If
WATCHTOWER_CLEANUP=true, removes the old image
The container is down for a few seconds during the restart. For homelab services, this is usually fine. If you’re running something where even brief downtime matters, schedule updates for off-hours or use monitoring mode instead.
Checking Update History
docker logs watchtower
The logs show each check, what was updated, and any errors. If you set up notifications, you’ll see the same information in your messaging app of choice.
The Tradeoff
Automatic updates mean occasionally landing on a version with a bug or a breaking change. This happens rarely for mature projects, but it does happen. The mitigation:
- Set notifications so you know when updates happen
- Keep backups of your container config directories (volumes) so you can roll back if needed
- Check your services occasionally after Watchtower runs to make sure everything’s still working
For most homelab services, the update-related risk is lower than the risk of running stale, unpatched images indefinitely. Watchtower is the right call.