One of the hidden maintenance costs of self-hosting is keeping everything updated. When you’re running 10, 20, or 50 Docker containers, manually pulling new images and recreating containers gets old fast. Miss an update and you might be running a version with known security vulnerabilities.

Watchtower solves this by automatically monitoring your running containers, pulling new images when they’re available, and gracefully restarting containers with the same configuration. Set it up once, and your homelab stays current without you lifting a finger.

What Is Watchtower?

Watchtower is a lightweight Docker container that watches your other containers. When it detects that a new image has been pushed to the registry, it pulls the image and restarts the container using the original docker run options or compose configuration.

Key features:

  • Automatic updates on a configurable schedule
  • Notifications via email, Slack, Discord, Telegram, Gotify, and more
  • Selective monitoring — update only the containers you choose
  • Cleanup — remove old images after updating
  • Rolling restarts — minimize downtime
  • Private registry support — works with any Docker registry

Prerequisites

  • Docker and Docker Compose installed on your server
  • Existing containers you want to keep updated

Step 1: Basic Setup

Create a directory and compose file:

mkdir -p ~/watchtower && cd ~/watchtower

Create docker-compose.yml:

services:
  watchtower:
    image: containrrr/watchtower:latest
    container_name: watchtower
    restart: unless-stopped
    environment:
      - TZ=America/New_York
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_SCHEDULE=0 0 4 * * *
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

Start it:

docker compose up -d

That’s it for the basics. Watchtower will now check for updates every day at 4 AM and automatically update any container that has a newer image available.

Step 2: Understanding the Schedule

The WATCHTOWER_SCHEDULE uses 6-field cron syntax (with seconds):

004smhdmeioaocnuyndourtantohyde(f0(oE((-m1fv002o-e--3n1wr55)t2ey99h)e))kd(a1(y-03-a16t),4S:u0n0=0A)M

Common schedules:

ScheduleCron Expression
Daily at 4 AM0 0 4 * * *
Every 6 hours0 0 */6 * * *
Sundays at 3 AM0 0 3 * * 0
Every 30 minutes0 */30 * * * *

Alternatively, use WATCHTOWER_POLL_INTERVAL for a simple interval in seconds:

- WATCHTOWER_POLL_INTERVAL=86400  # Every 24 hours

Tip: Schedule updates during low-traffic hours. Some updates require brief downtime during container restart.

Step 3: Set Up Notifications

Don’t update blindly — know what changed. Watchtower supports multiple notification channels.

Discord

environment:
  - WATCHTOWER_NOTIFICATIONS=shoutrrr
  - WATCHTOWER_NOTIFICATION_URL=discord://token@webhookid

Get the webhook URL from Discord (Server Settings → Integrations → Webhooks), then convert it:

#h#dtiDtSsiphcssooc:uro/tdr/r:ddr/ir/wsaecfbboochrrdodmeo.afkct@o:1Um2R/3La4:p5i6/webhooks/123456/abcdef

Telegram

environment:
  - WATCHTOWER_NOTIFICATIONS=shoutrrr
  - WATCHTOWER_NOTIFICATION_URL=telegram://bottoken@telegram?channels=chatid

Email

environment:
  - WATCHTOWER_NOTIFICATIONS=email
  - [email protected]
  - [email protected]
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.gmail.com
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587
  - [email protected]
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=app-password

Gotify (self-hosted)

environment:
  - WATCHTOWER_NOTIFICATIONS=shoutrrr
  - WATCHTOWER_NOTIFICATION_URL=gotify://gotify.example.com/tokenhere

Check the Shoutrrr docs for all supported services.

Step 4: Selective Updates (Monitor Mode vs Label Mode)

By default, Watchtower updates all running containers. This might not be what you want — some services (like databases) need careful upgrade planning.

Option A: Monitor Only (Don’t Auto-Update)

Set Watchtower to only notify, not update:

environment:
  - WATCHTOWER_MONITOR_ONLY=true

This is great if you want awareness without automation.

Option B: Label-Based Control

Only update containers with a specific label:

environment:
  - WATCHTOWER_LABEL_ENABLE=true

Then add the label to containers you want auto-updated:

# In your other docker-compose files:
services:
  uptime-kuma:
    image: louislam/uptime-kuma:latest
    labels:
      - "com.centurylinklabs.watchtower.enable=true"

Containers without the label are ignored.

Option C: Exclude Specific Containers

Keep auto-update on by default but exclude sensitive ones:

# On containers you DON'T want updated:
labels:
  - "com.centurylinklabs.watchtower.enable=false"

Recommendation: Use label mode (WATCHTOWER_LABEL_ENABLE=true) and explicitly opt in. This gives you full control over what gets auto-updated.

Step 5: Handle Databases Safely

Databases like PostgreSQL, MySQL, and Redis should not be auto-updated. Major version upgrades often require migration steps that Watchtower can’t handle.

Always exclude databases:

services:
  postgres:
    image: postgres:16-alpine  # Pin to major version
    labels:
      - "com.centurylinklabs.watchtower.enable=false"

For databases, pin to a major version (e.g., postgres:16-alpine not postgres:latest) and upgrade manually when ready.

Step 6: Private Registry Authentication

If you use private registries (GitHub Container Registry, GitLab, self-hosted), Watchtower needs credentials:

volumes:
  - /var/run/docker.sock:/var/run/docker.sock
  - ~/.docker/config.json:/config.json:ro

Or set registry credentials via environment:

environment:
  - REPO_USER=username
  - REPO_PASS=password

Complete Production Setup

Here’s a full production-ready compose file combining everything:

services:
  watchtower:
    image: containrrr/watchtower:latest
    container_name: watchtower
    restart: unless-stopped
    environment:
      - TZ=America/New_York
      # Schedule: daily at 4 AM
      - WATCHTOWER_SCHEDULE=0 0 4 * * *
      # Only update labeled containers
      - WATCHTOWER_LABEL_ENABLE=true
      # Clean up old images
      - WATCHTOWER_CLEANUP=true
      # Notifications via Discord
      - WATCHTOWER_NOTIFICATIONS=shoutrrr
      - WATCHTOWER_NOTIFICATION_URL=discord://token@webhookid
      # Include stopped containers
      - WATCHTOWER_INCLUDE_STOPPED=false
      # Timeout for stopping containers gracefully
      - WATCHTOWER_TIMEOUT=30s
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    # Resource limits
    deploy:
      resources:
        limits:
          memory: 128M

Checking Update Logs

View what Watchtower has been doing:

docker logs watchtower

You’ll see output like:

ttttiiiimmmmeeee====""""2222000022226666----00002222----11115555TTTT00004444::::00000000::::00011580ZZZZ""""lllleeeevvvveeeellll====iiiinnnnffffoooommmmssssgggg====""""FSCRotreuoemnpaodptviiinnnnegggw//iluumoppauttgiiiesmmleeaa--bmkkc/uu1umm2paa3t""i(maeb-ck1u2m3a):lwaittehstSIiGmTaEgReM"(abc123)"

Troubleshooting

Watchtower isn’t updating containers

  1. Check the schedule is correct (remember: 6-field cron with seconds)
  2. If using label mode, verify containers have the watchtower label
  3. Check Docker socket permissions: ls -la /var/run/docker.sock
  4. View logs: docker logs watchtower --tail 50

Container fails after update

Watchtower pulls the new image and recreates the container with the same config. If a new version introduces breaking changes:

# Roll back manually
docker stop container_name
docker rm container_name
docker run ... previous_image:tag  # or update your compose file

This is why notifications matter — you’ll know immediately what was updated.

High memory usage during updates

Large image pulls can spike memory. The 128M limit in our production config prevents Watchtower from consuming too much. Increase if you see OOM kills.

Updates cause downtime

For zero-downtime updates on critical services, consider:

  • Running redundant instances behind a load balancer
  • Using Watchtower’s --rolling-restart flag
  • Scheduling updates during maintenance windows

Alternatives to Watchtower

ToolApproach
WatchtowerAutomatic pull + restart
DiunNotification only (no auto-update)
OuroborosSimilar to Watchtower (less maintained)
RenovateUpdates compose files in Git (GitOps approach)

Watchtower is the most popular and actively maintained option for direct container updates.

Conclusion

Watchtower takes the most tedious part of self-hosting — keeping everything updated — and automates it completely. With label-based control and notifications, you get the convenience of auto-updates without losing control over sensitive services.

Start with monitor-only mode if you’re cautious, then gradually enable auto-updates for stable, well-tested containers. Your future self will thank you for not having to manually update 20 containers every week.

Related guides: