The Dilemma
You’ve got a bunch of Docker containers running. Some have security updates. Some have bug fixes. You know you should update them. But updating means downtime, and what if the new version breaks something?
Watchtower solves this by automatically pulling new images and restarting containers. But “automatically” is dangerous if you’re not careful. You can wake up to broken apps because a new version had incompatible changes.
Here’s how to do it safely.
What Watchtower Does
Watchtower monitors your Docker images. When a new version is pushed to the registry, it:
- Pulls the new image
- Stops the old container
- Starts a new one with the new image
- Cleans up the old image
All automatic. No human intervention needed.
The catch: if that new version breaks your app, you’re down until you notice and rollback.
Safe Configuration
Instead of blindly updating everything, use these strategies:
Strategy 1: Update only specific containers
version: '3.8'services: watchtower: image: containrrr/watchtower:latest volumes: - /var/run/docker.sock:/var/run/docker.sock command: --cleanup app nginx mysql-backup environment: WATCHTOWER_SCHEDULE: "0 3 * * *" WATCHTOWER_NOTIFICATION_URL: "ntfy://example.com/watchtower-alerts"The command line tells Watchtower to only update containers named app, nginx, and mysql-backup. Leave the database container alone.
Strategy 2: Don’t update untagged containers
Never run latest. Always specify a version:
# Badservices: postgres: image: postgres
# Goodservices: postgres: image: postgres:15.4With a pinned version, Watchtower won’t update your database unless you explicitly change the tag in compose.
services: postgres: image: postgres:15.4
# Auto-update ok — not critical infrastructure web-app: image: myapp:latest
watchtower: image: containrrr/watchtower:latest command: --cleanup web-appOnly the web app gets updated. The database is locked to 15.4.
Rollback Strategy
If an update breaks something, you need a fast rollback. The key: keep the old image around.
docker imagesShows you what images you have. Say an update broke your app:
docker ps -aFind the old container. Then:
docker run -d \ --name app-backup \ -v app-data:/data \ myapp:old-tagYou’re now running the old tag. Done.
But Watchtower deletes old images by default. Disable that:
watchtower: image: containrrr/watchtower:latest command: --cleanup app # Keep old images for 30 days environment: WATCHTOWER_CLEANUP: "true" WATCHTOWER_REMOVE_VOLUMES: "false"Wait, that’s not quite right. Watchtower deletes unused images after updating. To keep them:
watchtower: image: containrrr/watchtower:latest volumes: - /var/run/docker.sock:/var/run/docker.sock command: --cleanup # Don't remove old images # (just don't pass --cleanup or use --no-cleanup)Actually, simplest approach: use a tag that includes the date:
# In your CI/CD, tag images like this:docker tag myapp:build myapp:2025-12-23docker push myapp:2025-12-23Then in Compose:
services: app: image: myapp:2025-12-23When you need to rollback, just change the tag back:
services: app: image: myapp:2025-12-22And restart:
docker-compose up -dNotification on Update
Watchtower can notify you when it updates something. This is important — you want to know when things change.
watchtower: image: containrrr/watchtower:latest volumes: - /var/run/docker.sock:/var/run/docker.sock environment: # Update check at 3 AM WATCHTOWER_SCHEDULE: "0 3 * * *"
# Send notifications WATCHTOWER_NOTIFICATIONS: shoutrrr WATCHTOWER_NOTIFICATION_URL: "ntfy://example.com/watchtower" WATCHTOWER_NOTIFICATION_TEMPLATE: | {{if eq .Status "updated"}} Updated: {{ .Name }} Old: {{ .ImageId }} New: {{ .NewImageId }} {{end}}With ntfy.sh, you get a simple HTTP POST. You can also use:
- Gotify (self-hosted notification server)
- Slack
- Discord
Manual Testing Before Auto-Update
The safest approach: test updates manually first, then enable auto-update for proven versions.
- Spin up a test container with the new image
- Run your app for an hour, verify it works
- Once you’re confident, update Watchtower to use that version
# Test the new versiondocker run -d --name app-test myapp:new-versiondocker logs -f app-test
# If everything's good, update your Compose file# Change the tag from latest to new-version# Let Watchtower handle the restDangerous Updates to Avoid
Some updates you should never auto-update:
- Database versions (Postgres, MySQL — breaking changes are common)
- System dependencies (Java, Python runtimes)
- Message queues (RabbitMQ, Redis — state matters)
For these, use manual updates:
postgres: image: postgres:15.4 # No watchtower for this
app: image: myapp:latest # Ok for watchtower to update
redis: image: redis:7.0-alpine # No watchtower for thisCheck What Watchtower Would Update
Before deploying Watchtower, see what it would actually update:
docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ containrrr/watchtower \ --dry-run \ --debugThe --dry-run flag shows what would be updated without actually doing it.
The Schedule
watchtower: environment: # 3 AM daily WATCHTOWER_SCHEDULE: "0 3 * * *"
# Or cron format: Sunday at 2 AM # WATCHTOWER_SCHEDULE: "0 2 * * 0"Pick a time when you’re likely awake if something goes wrong. Not the middle of your work day (too disruptive), but not so late you’ll be asleep if there’s an issue.
The Paranoid Version
If you don’t trust Watchtower to not break things:
watchtower: image: containrrr/watchtower:latest volumes: - /var/run/docker.sock:/var/run/docker.sock environment: # Only update images, don't restart containers WATCHTOWER_MONITOR_ONLY: "true" WATCHTOWER_NOTIFICATIONS: gotify WATCHTOWER_NOTIFICATION_URL: "https://gotify.example.com"With MONITOR_ONLY, Watchtower just tells you when updates are available. You manually restart containers to pick them up.
Bottom line: Watchtower is great for keeping things up-to-date, but you need to think about what breaks and how fast you can fix it. Update frequently, but safely.