Keeping Docker containers up to date is one of those chores that sounds simple but quickly becomes a time sink when you’re running a dozen self-hosted services. Miss an update and you might leave a security hole open for weeks. That’s where Watchtower comes in — a lightweight container that monitors your running Docker containers, detects new image versions, and automatically pulls and restarts them for you.
Watchtower has been a staple of the homelab community for years, and for good reason: it’s dead simple to configure, surprisingly flexible, and it runs entirely inside Docker itself. This guide covers everything from a basic one-shot update run to a fully configured, notification-enabled automatic update setup — including how to exclude containers you don’t want touched.
What Is Watchtower?
Watchtower is an open-source project maintained by containrrr. It runs as a Docker container, watches the Docker socket, and polls configured registries for updated images. When it finds one, it:
- Pulls the new image
- Stops the running container
- Restarts it with the same runtime parameters (volumes, ports, env vars, etc.)
The whole cycle usually takes seconds and the container returns to service automatically. Old images are optionally cleaned up afterwards.
Watchtower works with images from Docker Hub, GitHub Container Registry, and private registries with credentials. It respects your existing docker-compose.yml parameters and does not require Compose — it reads directly from the running container’s config.
Prerequisites
Before you start, make sure you have:
- A Linux server with Docker installed (and optionally Docker Compose v2)
- Root or sudo access
- At least a few running containers to monitor
If you’re just getting started with Docker, a capable mini PC like the Intel NUC or Beelink Mini S12 makes an excellent low-power homelab host. For storage-heavy setups, consider pairing it with a NAS enclosure for Docker volumes.
Running Watchtower — Quick Start
The fastest way to see Watchtower in action is a one-shot manual run. This doesn’t start any scheduled polling — it simply checks all containers once, updates what needs updating, and exits.
| |
That’s it. Watchtower will scan your running containers, pull newer images where available, and restart those containers. After it finishes, the Watchtower container removes itself (--rm).
This mode is perfect for running on a schedule via cron, or as a quick “update everything now” command.
Setting Up Watchtower with Docker Compose
For a persistent, scheduled update setup, use Docker Compose. Create a directory and file:
| |
Paste this configuration:
| |
Start it:
| |
Key Environment Variables Explained
| Variable | Default | Description |
|---|---|---|
WATCHTOWER_POLL_INTERVAL | 86400 | Seconds between checks. 86400 = once per day |
WATCHTOWER_CLEANUP | false | Remove old images after update |
WATCHTOWER_INCLUDE_STOPPED | false | Also update stopped containers |
WATCHTOWER_INCLUDE_RESTARTING | false | Update containers in restarting state |
TZ | UTC | Timezone for logs and scheduling |
A 24-hour interval (86400 seconds) is a sensible default for most homelabs. If you prefer weekly updates, use 604800. For high-security environments where you want patches applied quickly, try 3600 (hourly).
Using a Cron Schedule Instead of Polling Interval
Watchtower supports cron-style scheduling via the WATCHTOWER_SCHEDULE variable, which gives you finer control over when updates run:
| |
The format is a 6-field cron expression: <seconds> <minutes> <hours> <day-of-month> <month> <day-of-week>.
Some useful examples:
0 0 4 * * *— Daily at 4 AM0 0 2 * * 0— Every Sunday at 2 AM0 0 3 * * 1-5— Weekdays at 3 AM0 0 4 1 * *— First day of every month at 4 AM
Running updates in the early morning minimizes disruption and gives you time to notice issues before your workday begins.
Excluding Containers from Automatic Updates
Not all containers should be auto-updated. Databases (Postgres, MariaDB, MySQL), production-critical apps, or services where you want to test upgrades first should be excluded.
There are two ways to exclude containers:
Option 1: Label on the Container
Add this label to any container you want Watchtower to ignore:
| |
Option 2: Watchtower Opt-In Mode
Flip the logic: instead of excluding specific containers, tell Watchtower to only update containers that explicitly opt in:
| |
Then add this label to every container you do want updated:
| |
This opt-in approach is safer for mature setups where you’re selective about automation. The opt-out approach (default) is more convenient for setups where you trust auto-updates broadly.
Setting Up Notifications
Watchtower can send notifications when it updates containers. This is essential — you want to know when something was changed. Supported services include:
- Email (SMTP)
- Slack
- Microsoft Teams
- Gotify (self-hosted push notifications)
- Shoutrrr (generic notification router)
Email Notifications
| |
Slack / Discord Webhook
For Slack-compatible webhooks (including Discord with /slack appended to the webhook URL):
| |
Gotify (Self-Hosted)
If you’re running Gotify for push notifications:
| |
Gotify is excellent for homelabbers who want self-hosted notifications. A Raspberry Pi or any spare SBC can run Gotify alongside Watchtower without breaking a sweat.
Handling Private Registries
If you pull images from private registries (GitHub Container Registry, your own registry, etc.), Watchtower needs credentials to check for updates.
Method 1: Docker Config File
Log into the registry on the host and mount the Docker config:
| |
Then mount the config file into Watchtower:
| |
Method 2: Environment Variables Per-Registry
For simple use cases with one registry:
| |
Complete Production-Ready docker-compose.yml
Here’s a full setup combining everything above — cron schedule, cleanup, opt-in labels, and Gotify notifications:
| |
Monitoring Watchtower Logs
To verify Watchtower is running and see what it’s doing:
| |
A healthy log output looks like this:
time="2026-02-18T04:00:01Z" level=info msg="Checking all containers (except explicitly disabled)"
time="2026-02-18T04:00:05Z" level=info msg="Found new nginx:alpine (sha256:abc123...)"
time="2026-02-18T04:00:12Z" level=info msg="Stopping /nginx (nginx:alpine) with SIGTERM"
time="2026-02-18T04:00:14Z" level=info msg="Creating /nginx"
time="2026-02-18T04:00:14Z" level=info msg="Session done" Failed=0 Scanned=12 Updated=1 Total=12
The Session done line is the summary — check Failed=0 to confirm no errors.
Running Watchtower on Specific Containers Only (CLI)
You can tell Watchtower (in --run-once mode) to target specific containers by name:
| |
This is great for testing or manually updating a subset of your stack without touching everything else.
Best Practices and Tips
1. Always Use WATCHTOWER_CLEANUP=true
Old Docker images accumulate fast. Each update leaves behind the previous image layer, which can eat gigabytes over time. Setting cleanup to true removes old images automatically after a successful update.
2. Pin Critical Service Versions
For databases and anything stateful, pin your image tags in docker-compose.yml:
| |
Watchtower won’t update a pinned tag — it only acts when a tag like latest or stable points to a new digest. This gives you control over major version upgrades while still automating patch updates on everything else.
3. Test Updates During Off-Hours
Use the cron schedule to run updates at 4 AM or similar. Container restarts take seconds, but you still don’t want a surprise restart in the middle of a video stream or an active Nextcloud sync.
4. Don’t Auto-Update Watchtower Itself
This is a common gotcha — Watchtower will try to update itself too, and while it handles this gracefully most of the time, it can occasionally cause the container to briefly disappear. If that concerns you, pin the Watchtower image tag or add a label to exclude it from its own checks (yes, it respects its own labels).
5. Use Portainer or a Dashboard for Visibility
If you’re running Portainer or a dashboard like Homepage, you can see at a glance which containers are on what image version. This pairs nicely with Watchtower’s update log to build a clear picture of your update history.
Watchtower vs Manual Updates vs Renovate
Watchtower isn’t the only way to manage container updates. Here’s a quick comparison:
| Method | Effort | Control | Automation |
|---|---|---|---|
| Watchtower | Zero | Low-Medium | Full automatic |
Manual (docker pull) | High | Full | None |
| Renovate Bot | Medium setup | Full | PR-based |
| Diun | Low | Medium | Notifies only |
Diun (Docker Image Update Notifier) is worth mentioning as an alternative to Watchtower’s notification mode — it only notifies you about available updates and never actually updates anything, which some people prefer for maximum control. Think of it as Watchtower with the training wheels on.
Renovate is the gold standard if you manage your Docker Compose files in a Git repository — it opens pull requests for version bumps, just like it does for npm or Python packages. Great for teams; overkill for most homelabs.
For most self-hosters, Watchtower in a sensible configuration (cron schedule, cleanup enabled, critical databases excluded) hits the sweet spot between safety and convenience.
Troubleshooting
Watchtower isn’t updating a container
- Check the container name:
docker ps --format '{{.Names}}' - Verify Watchtower can see it:
docker logs watchtower | grep "container-name" - Check if the image uses a pinned digest or fixed tag — Watchtower can only update
latest-style tags - If using opt-in mode (
WATCHTOWER_LABEL_ENABLE=true), confirm the label is set
Got permission denied while trying to connect to the Docker daemon socket
Watchtower needs access to /var/run/docker.sock. If you’re running Watchtower as a non-root user, add it to the docker group or run it with user: root.
Container restarts but uses old image
This usually means Docker has the new image cached but the old container ID is being reused. Try:
| |
Notifications not arriving
Test your notification URL independently first (most services have test endpoints). Also check WATCHTOWER_DEBUG=true in Watchtower’s environment to see detailed notification request/response logs.
Conclusion
Watchtower is one of those tools that earns its place on every homelab server. The setup takes under five minutes, the resource footprint is negligible, and the time it saves — not to mention the security patches it applies automatically — makes it a no-brainer.
Start with the simple Docker run command to see it in action, then move to a Compose-based setup with a cron schedule and cleanup enabled. Add notifications so you know when updates happen, exclude your databases from automatic updates, and you’ll have a solid, low-maintenance container update pipeline.
For the hardware side of your homelab — mini PCs, external drives, network switches, and UPS battery backups — the SelfHostWise hardware guides cover the best options at every price point.
Now go let Watchtower handle the updates. You’ve got better things to do.