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.

💡 This article contains affiliate links. If you buy through them, we earn a small commission at no extra cost to you. Learn more.

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:

  1. Pulls the new image
  2. Stops the running container
  3. 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.

1
2
3
4
docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower \
  --run-once

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:

1
2
mkdir -p /opt/watchtower
nano /opt/watchtower/docker-compose.yml

Paste this configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
services:
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_POLL_INTERVAL=86400
      - WATCHTOWER_INCLUDE_STOPPED=false
      - TZ=Europe/Berlin

Start it:

1
2
cd /opt/watchtower
docker compose up -d

Key Environment Variables Explained

VariableDefaultDescription
WATCHTOWER_POLL_INTERVAL86400Seconds between checks. 86400 = once per day
WATCHTOWER_CLEANUPfalseRemove old images after update
WATCHTOWER_INCLUDE_STOPPEDfalseAlso update stopped containers
WATCHTOWER_INCLUDE_RESTARTINGfalseUpdate containers in restarting state
TZUTCTimezone 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:

1
2
3
4
environment:
  - WATCHTOWER_SCHEDULE=0 0 4 * * *   # Run at 4:00 AM every day
  - WATCHTOWER_CLEANUP=true
  - TZ=Europe/Berlin

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 AM
  • 0 0 2 * * 0 — Every Sunday at 2 AM
  • 0 0 3 * * 1-5 — Weekdays at 3 AM
  • 0 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:

1
2
3
4
5
services:
  postgres:
    image: postgres:16
    labels:
      - "com.centurylinklabs.watchtower.enable=false"

Option 2: Watchtower Opt-In Mode

Flip the logic: instead of excluding specific containers, tell Watchtower to only update containers that explicitly opt in:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
services:
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_LABEL_ENABLE=true   # Only update containers with the label
      - WATCHTOWER_SCHEDULE=0 0 4 * * *
      - TZ=Europe/Berlin

Then add this label to every container you do want updated:

1
2
labels:
  - "com.centurylinklabs.watchtower.enable=true"

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

1
2
3
4
5
6
7
8
environment:
  - WATCHTOWER_NOTIFICATION_EMAIL_FROM=watchtower@yourdomain.com
  - WATCHTOWER_NOTIFICATION_EMAIL_TO=you@yourdomain.com
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.yourdomain.com
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=watchtower@yourdomain.com
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=yourpassword
  - WATCHTOWER_NOTIFICATION_EMAIL_DELAY=2

Slack / Discord Webhook

For Slack-compatible webhooks (including Discord with /slack appended to the webhook URL):

1
2
3
environment:
  - WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
  - WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=watchtower

Gotify (Self-Hosted)

If you’re running Gotify for push notifications:

1
2
3
4
environment:
  - WATCHTOWER_NOTIFICATIONS=gotify
  - WATCHTOWER_NOTIFICATION_GOTIFY_URL=https://gotify.yourdomain.com
  - WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN=your-gotify-app-token

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:

1
docker login ghcr.io

Then mount the config file into Watchtower:

1
2
3
4
5
volumes:
  - /var/run/docker.sock:/var/run/docker.sock
  - /root/.docker/config.json:/config.json
environment:
  - DOCKER_CONFIG=/config.json

Method 2: Environment Variables Per-Registry

For simple use cases with one registry:

1
2
3
environment:
  - REPO_USER=your-username
  - REPO_PASS=your-password

Complete Production-Ready docker-compose.yml

Here’s a full setup combining everything above — cron schedule, cleanup, opt-in labels, and Gotify notifications:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
services:
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      # Scheduling
      - WATCHTOWER_SCHEDULE=0 0 4 * * *
      - TZ=Europe/Berlin

      # Behavior
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_INCLUDE_STOPPED=false
      - WATCHTOWER_LABEL_ENABLE=false   # Set true for opt-in mode

      # Logging
      - WATCHTOWER_DEBUG=false
      - WATCHTOWER_LOG_LEVEL=info

      # Notifications (Gotify)
      - WATCHTOWER_NOTIFICATIONS=gotify
      - WATCHTOWER_NOTIFICATION_GOTIFY_URL=https://gotify.yourdomain.com
      - WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN=your-token-here

Monitoring Watchtower Logs

To verify Watchtower is running and see what it’s doing:

1
2
3
4
5
# Follow live logs
docker logs -f watchtower

# Last 50 lines
docker logs --tail 50 watchtower

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:

1
2
3
4
docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower \
  --run-once nginx jellyfin uptime-kuma

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:

1
image: postgres:16.2

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:

MethodEffortControlAutomation
WatchtowerZeroLow-MediumFull automatic
Manual (docker pull)HighFullNone
Renovate BotMedium setupFullPR-based
DiunLowMediumNotifies 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

  1. Check the container name: docker ps --format '{{.Names}}'
  2. Verify Watchtower can see it: docker logs watchtower | grep "container-name"
  3. Check if the image uses a pinned digest or fixed tag — Watchtower can only update latest-style tags
  4. 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:

1
docker compose pull && docker compose up -d --force-recreate

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.