Report this

What is the reason for this report?

Docker Container Images with Watchtower on Ubuntu

Updated on March 5, 2026
Docker Container Images with Watchtower on Ubuntu

Introduction

Watchtower is a container that monitors your running Docker containers and updates them when newer images are available in their registries. It connects to the Docker socket, polls image registries at a configurable interval, and when it detects a digest change it pulls the new image, stops the running container, and starts a replacement with the updated image. This automation is useful for homelabs, development environments, and low-criticality workloads where you want containers to stay current without manual pulls and restarts.

The Watchtower project was archived by the owner in December 2025 and is now read-only. The repository remains available, issues and discussions are locked, and the last stable release is v1.7.1. Community discussion (including on r/docker) often asks whether Watchtower is deprecated or discontinued. As of this writing, the image continues to be available on Docker Hub and GHCR, and many users still run it for non-critical environments. For new deployments or production pipelines with rollback requirements, consider alternatives such as Renovate or CI/CD-driven updates, which are covered later in this tutorial.

Watchtower is a good fit when you want hands-off image updates on a single host or small fleet, with optional notifications and minimal configuration. It is less appropriate for production environments that require change approval, rollback, or zero-downtime orchestration; in those cases, a CI/CD-integrated approach is more appropriate.

Prerequisites

To complete this tutorial, you will need:

  • An Ubuntu server (22.04 or later), set up according to the initial server setup guide for Ubuntu, with a non-root user with sudo privileges and a firewall enabled.
  • Docker installed on your server, following Steps 1 and 2 of how to install and use Docker on Ubuntu 22.04.
    • If you plan to test updates with your own image on Docker Hub, you will need a Docker Hub account and familiarity with pushing images. You can create a custom image by following Steps 5 through 8 of the same Docker tutorial.
  • Docker Compose installed. On current Ubuntu versions, Docker Compose v2 is the default (invoked as docker compose, without a hyphen). Follow Step 1 of how to install and use Docker Compose on Ubuntu 22.04 if needed.
  • For Step 6 (Watchtower HTTP API), curl installed so you can trigger on-demand updates (sudo apt install curl if not present).
  • If you plan to set up email notifications through Gmail, a Gmail account with 2-Step Verification enabled and an application-specific App Password. Google has deprecated plain password auth for SMTP; App Passwords remain the supported method.

Key Takeaways

  • Watchtower runs as a container that mounts the Docker socket and polls image registries at a set interval; when the remote digest differs from the running image, it pulls the new image, stops the container, and starts a new one with the updated image.
  • You can limit which containers are updated by passing container names as arguments to Watchtower or by using labels (com.centurylinklabs.watchtower.enable=true with --label-enable, or com.centurylinklabs.watchtower.enable=false to exclude from a watch-all instance).
  • Private registry authentication is supported via REPO_USER and REPO_PASS (Docker Hub, GHCR) or via registry-specific credential helpers (e.g., AWS ECR); never hardcode credentials in Compose files committed to version control.
  • Notifications can be sent via email (SMTP), Slack webhook, or generic webhook (shoutrrr) so you are informed when updates are detected or applied.
  • The Watchtower HTTP API (enabled with --http-api-update and protected by WATCHTOWER_HTTP_API_TOKEN) allows on-demand update triggers; do not expose the API port publicly without authentication and firewall rules.
  • Monitor-only mode (--monitor-only or WATCHTOWER_MONITOR_ONLY=true) checks for new images and sends notifications without restarting containers; you can then apply updates manually or via a one-off --run-once run.
  • For production pipelines that require rollback, approvals, or zero-downtime deployments, prefer CI/CD-integrated tools (e.g., Renovate) over Watchtower; Watchtower is well-suited for homelab, dev, and low-criticality production.

Understanding How Watchtower Works

Watchtower runs inside a Docker container and gains control over the host’s containers by mounting the Docker daemon socket (/var/run/docker.sock) into the Watchtower container. It does not install any software on the host beyond what you use to run Docker. Watchtower requires the Docker socket mount because it communicates directly with the Docker daemon API to list containers, inspect their image metadata, pull new images, and issue stop and start commands, the same operations you perform manually with docker pull and docker run.

The update lifecycle is a polling loop. On each cycle, Watchtower inspects the containers it is configured to monitor (all running containers, or only those matching names or labels). For each container it queries the image registry (Docker Hub, GHCR, ECR, etc.) for the digest of the image tag in use. It compares that digest to the digest of the image that the running container was created from. (A digest is a unique SHA256 hash that identifies an exact image build. Two images with the same tag can have different digests if the image was rebuilt and pushed again.) If they match, no action is taken and Watchtower sleeps until the next poll. If they differ, Watchtower pulls the new image, stops the running container, and starts a new container from the updated image, using the same image name and tag and the same parameters the container was originally started with, including environment variables, volumes, networks, port bindings, and restart policy. No configuration is lost during an update. Optionally, Watchtower can remove the old image after a successful update (--cleanup) to avoid accumulating unused images. For more on cleaning up images and containers, see how to remove Docker images, containers, and volumes.

You can control how often the loop runs. When using a time-based poll interval, the default is 86400 seconds (24 hours). When using cron-style scheduling with --schedule, you define exactly when checks run (e.g., daily at 4:00 AM). You cannot use both --interval and --schedule at once. A typical Watchtower container uses roughly 10-15 MB of memory.

Plain-text view of the lifecycle:

  +------------------+
  | Container Running|
  +--------+---------+
           |
           v
  +------------------+
  |  Poll Registry   |
  |  (digest check)  |
  +--------+---------+
           |
           v
  +------------------+
  | Compare Digest   |
  +--------+---------+
           |
     +-----+-----+
     |           |
     v           v
 No Change    Change Detected
     |           |
     v           v
 +-------+   +--------+
 | Sleep |   | Pull   |
 | until |   | then   |
 | next  |   | Stop   |
 | poll  |   | then   |
 +-------+   |Restart |
 +-------+   +--------+

Step 1 - Running Watchtower With Docker Run

You need at least one running container for Watchtower to monitor. The following example creates a long-lived container from the official Ubuntu image so you can later observe Watchtower updating it.

Create a container that stays running:

docker run -d \
  --name ubuntu-container \
  ubuntu \
  sleep infinity

Start Watchtower and point it at that container. Use a pinned image tag for reproducibility; the current stable release is 1.7.1:

docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower:1.7.1 \
  ubuntu-container
  • -d: run in detached mode.
  • --name watchtower: container name for management.
  • -v /var/run/docker.sock:/var/run/docker.sock: required so Watchtower can talk to the Docker daemon.
  • containrrr/watchtower:1.7.1: pinned image; replace with a newer tag if available.
  • ubuntu-container: only this container is watched; omit to watch all running containers.

Docker will pull the image if it is not present. Example output:

# Output
Unable to find image 'containrrr/watchtower:1.7.1' locally
1.7.1: Pulling from containrrr/watchtower
...
Status: Downloaded newer image for containrrr/watchtower:1.7.1
container_id

Verify both containers are running:

docker ps
# Output
CONTAINER ID   IMAGE                        COMMAND                  CREATED         STATUS         PORTS      NAMES
watchtower_id   containrrr/watchtower:1.7.1   "/watchtower ubuntu-…"   N seconds ago   Up N seconds   8080/tcp   watchtower
ubuntu_id   ubuntu                      "sleep infinity"         N seconds ago   Up N seconds              ubuntu-container

When a newer image is pushed for the tag your container uses, Watchtower will detect it on the next poll, pull the image, stop ubuntu-container, and start a new container with the updated image. By default, the poll interval is 86400 seconds (24 hours) unless you override it with --interval.

Verify that Watchtower started correctly and is monitoring your container by checking its logs:

docker logs watchtower

You should see output similar to this on a clean start:

# Output
time="2024-01-15T10:00:00Z" level=info msg="Starting Watchtower and scheduling first run: 2024-01-15 10:00:00 +0000 UTC"
time="2024-01-15T10:00:00Z" level=info msg="Checking all containers (except explicitly disabled)"
time="2024-01-15T10:00:00Z" level=info msg="Found new containrrr/watchtower:1.7.1 image (sha256:...)"

If you see level=error msg="Could not connect to Docker daemon", the socket mount is missing or incorrect. Stop the container, verify the -v /var/run/docker.sock:/var/run/docker.sock flag, and start it again.

If you see level=info msg="Scheduling first run" followed by no further output, Watchtower is running normally and waiting for the next poll interval. You will see additional log lines only when a poll cycle runs or an update is detected.

Note: Watchtower automatically restarts containers when it applies updates. In production environments, test restart behavior and dependencies before enabling automatic updates.

To stop and remove the demo containers:

docker stop watchtower ubuntu-container
docker rm watchtower ubuntu-container

Common errors and what they mean

Cannot connect to the Docker daemon The -v /var/run/docker.sock:/var/run/docker.sock mount is missing or the socket path is wrong. Verify with ls -la /var/run/docker.sock on the host. If your Docker installation uses a different socket path, update the volume mount accordingly.

Error response from daemon: manifest unknown The image tag Watchtower is checking no longer exists in the registry, or the registry returned a 404. Verify the image name and tag with docker inspect <container_name> --format '{{.Config.Image}}'.

Error response from daemon: toomanyrequests Docker Hub rate limiting has been triggered (100 unauthenticated pulls per 6 hours per IP). Authenticate Watchtower to Docker Hub using REPO_USER and REPO_PASS (covered in Step 4) to raise the limit to 200 per 6 hours for free accounts, or use a paid Docker Hub subscription for higher limits.

Container restarts but application is unreachable after update The updated image may have changed a port, entrypoint, or environment variable. Check docker logs <container_name> for startup errors after the update. Watchtower does not perform health checks; if you need update rollback on failure, use a CI/CD pipeline instead.

Step 2 - Monitoring Specific Containers With Labels

You can control which containers Watchtower updates in two ways: by passing container names as arguments (as in Step 1) or by using labels. Label-based filtering is useful when you have many containers and want to opt in or opt out without listing names.

Opt-in: only update containers that have the enable label

Add the label com.centurylinklabs.watchtower.enable=true to containers you want Watchtower to update. Run Watchtower with --label-enable so it only monitors containers that have this label set to true.

Example: create two containers, but only attach the label to one:

# Container that Watchtower will update (has enable=true)
docker run -d \
  --name app-container \
  --label com.centurylinklabs.watchtower.enable=true \
  nginx:alpine

# Container that Watchtower will ignore (no label)
docker run -d \
  --name other-container \
  nginx:alpine

Start Watchtower with label-based filtering (no container names as arguments):

docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower:1.7.1 \
  --label-enable

Only app-container will be considered for updates. Confirm the label is set on the correct container:

docker inspect app-container --format '{{json .Config.Labels}}'
# Output
{"com.centurylinklabs.watchtower.enable":"true"}
docker inspect other-container --format '{{json .Config.Labels}}'
# Output
{}

Opt-out: watch all except containers with the disable label

If you do not use --label-enable, Watchtower can watch all running containers by default. To exclude specific ones, set com.centurylinklabs.watchtower.enable=false on those containers:

docker run -d \
  --name no-update-container \
  --label com.centurylinklabs.watchtower.enable=false \
  nginx:alpine

When Watchtower is run without --label-enable and without a list of container names, it will watch all containers except those with com.centurylinklabs.watchtower.enable=false.

Run Watchtower in watch-all mode to monitor every container except those explicitly excluded:

docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower:1.7.1
# No --label-enable flag and no container name arguments.
# Watchtower watches all running containers except those labelled enable=false.

Confirm which containers Watchtower is ignoring by checking its logs after startup:

docker logs watchtower

A container with com.centurylinklabs.watchtower.enable=false will appear in the logs as skipped during each poll cycle.

Docker Compose examples for labels

Opt-in pattern: only the service with the label is watched when Watchtower uses --label-enable:

services:
  app:
    image: nginx:alpine
    container_name: app-container
    labels:
      - com.centurylinklabs.watchtower.enable=true

  other:
    image: nginx:alpine
    container_name: other-container
    # No label — Watchtower will ignore this container.

  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --label-enable --interval 300

Opt-out pattern: watch all except the service with enable=false:

services:
  app:
    image: nginx:alpine
    container_name: app-container
    # No label — Watchtower will update this container.

  no-update:
    image: nginx:alpine
    container_name: no-update-container
    labels:
      - com.centurylinklabs.watchtower.enable=false
    # Explicitly excluded from updates.

  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --interval 300
    # No --label-enable flag: watches all containers except those with enable=false.

Step 3 - Using Watchtower With Docker Compose

You can run Watchtower and your application containers from a single Compose file. The following example uses the label-based opt-in pattern and a 30-second poll interval for testing. Docker Compose v2 does not require a top-level version key; omit it.

Create a project directory and a Compose file:

mkdir -p ~/watchtower
cd ~/watchtower
nano docker-compose.yml

Paste the following (replace sammy with your Docker Hub username if using a private image):

services:
  ubuntu:
    image: ubuntu
    container_name: ubuntu-container
    command: sleep infinity
    labels:
      - com.centurylinklabs.watchtower.enable=true

  custom-test:
    image: sammy/ubuntu-nodejs
    container_name: test-container
    command: sleep infinity
    labels:
      - com.centurylinklabs.watchtower.enable=true

  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --interval 30 --label-enable --cleanup

Start the stack:

docker compose up -d

Verify Watchtower started and is monitoring the correct containers:

docker compose logs watchtower

On a successful start with label filtering active, you will see lines similar to:

# Output
watchtower  | time="..." level=info msg="Starting Watchtower and scheduling first run: ..."
watchtower  | time="..." level=info msg="Checking containers with enable label"
watchtower  | time="..." level=info msg="ubuntu-container (ubuntu): up to date"
watchtower  | time="..." level=info msg="test-container (sammy/ubuntu-nodejs): up to date"

Containers without the com.centurylinklabs.watchtower.enable=true label will not appear in these log lines, confirming the filter is working.

After changing the Compose file (e.g., adding services or changing the Watchtower command), recreate containers:

docker compose up -d --force-recreate

Only services with com.centurylinklabs.watchtower.enable=true are monitored. The --cleanup flag removes old images after a successful update to limit disk use. For more on keeping images lean, see how to optimize Docker images for production.

Step 4 - Authenticating With Private Registries

Watchtower can pull images from private registries. You provide credentials via environment variables or Docker config; the exact method depends on the registry.

Docker Hub (private repos)

Set REPO_USER and REPO_PASS (Docker Hub username and password or access token). In Compose:

services:
  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      REPO_USER: your_dockerhub_username
      REPO_PASS: your_dockerhub_password_or_token
    command: --interval 3600 --label-enable

AWS ECR

Watchtower can authenticate to AWS ECR using the amazon-ecr-credential-helper. The credential helper must be installed on the host and configured in Docker’s credential store. Watchtower then uses the host’s Docker config by mounting it into the container.

Install the credential helper on the host:

sudo apt install amazon-ecr-credential-helper

Add the helper to Docker’s credential config. Create or edit ~/.docker/config.json:

{
  "credHelpers": {
    "your_account_id.dkr.ecr.us-east-1.amazonaws.com": "ecr-login"
  }
}

Mount the Docker config into the Watchtower container so it can use the credential helper, and provide AWS credentials via environment variables:

services:
  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - $HOME/.docker/config.json:/config/config.json:ro
    environment:
      DOCKER_CONFIG: /config
      AWS_ACCESS_KEY_ID: your_access_key
      AWS_SECRET_ACCESS_KEY: your_secret_key
      AWS_REGION: us-east-1
    command: --interval 3600 --label-enable

Note: The IAM user or role must have at minimum the ecr:GetAuthorizationToken, ecr:BatchGetImage, and ecr:GetDownloadUrlForLayer permissions. Without these, Watchtower will log an auth error and skip the pull.

GitHub Container Registry (GHCR)

Use a GitHub username and a Personal Access Token (PAT) with read:packages as REPO_USER and REPO_PASS:

services:
  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      REPO_USER: your_github_username
      REPO_PASS: your_github_pat
    command: --interval 3600 --label-enable

Warning: Do not hardcode credentials in docker-compose.yml that is committed to version control. Use Docker secrets or a .env file that is listed in .gitignore, and reference it with env_file.

Example using a .env file and Compose:

.env (add to .gitignore):

REPO_USER=your_username
REPO_PASS=your_secret

docker-compose.yml:

services:
  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    env_file:
      - .env
    command: --interval 3600 --label-enable

Step 5 - Configuring Notifications (Email, Slack, Webhook)

Watchtower can send notifications when it starts, when it finds updates, and when it applies them. It uses shoutrrr for multiple backends. Configure one or more notification types via environment variables.

Email (Gmail SMTP)

Use Gmail with an App Password (plain password auth is deprecated by Google). Set the notification type to email and the SMTP variables:

services:
  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      WATCHTOWER_NOTIFICATIONS: email
      WATCHTOWER_NOTIFICATION_EMAIL_FROM: your_gmail@gmail.com
      WATCHTOWER_NOTIFICATION_EMAIL_TO: recipient@example.com
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER: smtp.gmail.com
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT: 587
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER: your_gmail@gmail.com
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD: gmail_app_password
    command: --interval 300 --label-enable

Slack

Use a Slack Incoming Webhook URL:

services:
  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      WATCHTOWER_NOTIFICATIONS: slack
      WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL: https://hooks.slack.com/services/YOUR/WEBHOOK/PATH
    command: --interval 300 --label-enable

Generic webhook (shoutrrr)

Use a generic webhook URL (e.g., for Discord, Telegram, or a custom endpoint). Format depends on the service; see shoutrrr documentation:

services:
  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      WATCHTOWER_NOTIFICATIONS: shoutrrr
      WATCHTOWER_NOTIFICATION_URL: generic+https://your-webhook-endpoint.example.com/notify
    command: --interval 300 --label-enable

What a Watchtower notification looks like

Watchtower sends one notification per update cycle in which at least one container was updated. A Slack message will look similar to:

Watchtower Updates on hostname

Updated containers:
- ubuntu-container (ubuntu:latest): updated from sha256:abc123 to sha256:def456
- test-container (sammy/ubuntu-nodejs:latest): updated from sha256:111aaa to sha256:222bbb

Watchtower v1.7.1

If no containers were updated during a poll cycle, no notification is sent. Notifications fire only when an update is detected and applied (or detected in monitor-only mode). If you do not receive a notification after a push, check docker logs watchtower for errors related to SMTP auth or webhook delivery.

Monitor-only with notifications

To only be notified of available updates without applying them, add:

environment:
  WATCHTOWER_MONITOR_ONLY: "true"

Then apply updates manually when you choose (e.g., with a one-off docker run ... --run-once or via the HTTP API in Step 6).

Step 6 - Using the Watchtower HTTP API for On-Demand Updates

Watchtower can expose an HTTP API so you can trigger an update cycle on demand instead of waiting for the next poll. This is useful for CI/CD or scripts.

Enable the API with --http-api-update and set a token for authentication:

docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -p 8080:8080 \
  -e WATCHTOWER_HTTP_API_UPDATE=true \
  -e WATCHTOWER_HTTP_API_TOKEN=your_secret_token \
  containrrr/watchtower:1.7.1

To enable the HTTP API in a Compose file, add the port mapping and environment variables to the Watchtower service:

services:
  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      WATCHTOWER_HTTP_API_UPDATE: "true"
      WATCHTOWER_HTTP_API_TOKEN: your_secret_token
    command: --label-enable

Note: Binding to 127.0.0.1:8080 instead of 0.0.0.0:8080 restricts the API to localhost only, which is the safest default on a single-host setup. If you need to call the API from another host, place it behind a reverse proxy with TLS rather than exposing port 8080 directly.

Trigger an update with curl:

curl -H "Authorization: Bearer your_secret_token" \
  http://localhost:8080/v1/update

Example response:

# Output
{"updated":["ubuntu-container"],"fresh":[],"failed":[],"skipped":[]}

If no containers needed updates:

# Output
{"updated":[],"fresh":["ubuntu-container"],"failed":[],"skipped":[]}

Example: triggering Watchtower from a CI/CD pipeline after an image push

In a GitHub Actions workflow, you can trigger Watchtower immediately after pushing a new image rather than waiting for the next poll:

# .github/workflows/deploy.yml (relevant step only)
- name: Trigger Watchtower update
  run: |
    curl -f -H "Authorization: Bearer ${{ secrets.WATCHTOWER_TOKEN }}" \
      https://your-host/v1/update

Store WATCHTOWER_TOKEN as a GitHub Actions secret. The -f flag causes curl to exit with a non-zero status if the HTTP response is an error, so the workflow step fails visibly if the trigger does not reach Watchtower.

In normal daemon mode, Watchtower keeps running and polls on an interval (or schedule). With --http-api-update, it can still run periodically if you also set --http-api-periodic-polls; otherwise the API is the only way to trigger checks. The --run-once flag is different: it runs one update cycle and then the container exits (useful for one-off jobs or cron).

Note: The HTTP API listens on port 8080 by default. Do not expose this port publicly without authentication (token) and firewall rules; otherwise anyone who can reach the port could trigger updates.

Watchtower Configuration Reference

Common flags and environment variables:

Flag Environment Variable Default Description
--interval WATCHTOWER_POLL_INTERVAL 86400 Poll interval in seconds. Cannot be used with --schedule.
--schedule WATCHTOWER_SCHEDULE - Cron expression (6 fields) for when to check. Cannot be used with --interval.
--cleanup WATCHTOWER_CLEANUP false Remove old images after updating.
--label-enable WATCHTOWER_LABEL_ENABLE false Only monitor containers with com.centurylinklabs.watchtower.enable=true.
--monitor-only WATCHTOWER_MONITOR_ONLY false Only check and notify; do not restart containers.
--run-once WATCHTOWER_RUN_ONCE false Run one update cycle and exit.
--http-api-update WATCHTOWER_HTTP_API_UPDATE false Enable HTTP API for triggering updates.
--http-api-periodic-polls WATCHTOWER_HTTP_API_PERIODIC_POLLS false When using the HTTP API, also continue polling on the configured interval or schedule. Without this flag, only API-triggered checks run.
- WATCHTOWER_HTTP_API_TOKEN - Token for HTTP API authentication.
- WATCHTOWER_NOTIFICATIONS - Notification type: email, slack, shoutrrr, etc.
- REPO_USER - Username for registry auth (e.g., Docker Hub, GHCR).
- REPO_PASS - Password or token for registry auth.

When both a flag (e.g., in command:) and an environment variable are set, the flag takes precedence.

Watchtower vs. Alternatives

Tool Update Method Registry Support Notifications Best For
Watchtower Polls host Docker, pulls and restarts Docker Hub, ECR, GHCR, others Email, Slack, webhook (shoutrrr) Homelab, dev, low-criticality production
What’s Up Docker (WUD) Polls registries, can report or trigger Multiple registries Webhooks, Slack, etc. Visibility and reporting; can trigger updates
Renovate PR-based, updates in repo Any registry used by repo GitHub/GitLab notifications CI/CD, production, rollback and approval workflows
Portainer UI-driven, manual or scheduled Any registry Docker can use Built-in and webhook Teams that prefer a UI over CLI/Compose

Watchtower

Runs on the same host as your containers, uses the Docker socket, and restarts containers when it detects new image digests. It is simple to deploy and fits homelabs and dev environments well. It does not integrate with Git or CI/CD, so there is no built-in rollback or approval step.

What’s Up Docker (WUD)

Focuses on detecting available updates and notifying you (or calling webhooks). It can integrate with Watchtower or other runners to perform the actual update, so it is often used for visibility and control rather than as a direct replacement.

Renovate

Works by opening pull requests (or merge requests) that update image references in your repo. Your pipeline builds and deploys after merge. This gives you review, rollback via Git, and fits production pipelines with change control.

Portainer

Provides a web UI for managing containers and images. It can update containers from the UI or on a schedule. It is a good fit when operators prefer a GUI and already use Portainer for day-to-day management.

Watchtower is well-suited for homelab, dev, and low-criticality production where automatic restarts are acceptable. For production pipelines that need rollback, approvals, or zero-downtime deployments, a CI/CD-integrated tool like Renovate is more appropriate.

FAQ

Q: Is Docker Watchtower deprecated?
A: The upstream containrrr/watchtower repository was archived by the owner in December 2025 and is read-only. The project is not actively maintained. The Docker image remains available, and many users still run it for non-critical workloads. For new or production-critical deployments, consider alternatives that are actively maintained and support rollback and approval workflows.

Q: What happened to the Watchtower Docker project?
A: The maintainers archived the GitHub repository; no new releases are planned. The last stable tag is v1.7.1. Community discussion (e.g., on r/docker) reflects ongoing use in homelabs and dev, plus interest in forks or alternatives for production.

Q: How does Watchtower work with Docker?
A: Watchtower runs as a container that mounts the host’s Docker socket. It periodically queries image registries for the digest of the image tag each monitored container uses. If the remote digest differs from the one used by the running container, Watchtower pulls the new image, stops the container, and starts a new container from the updated image. For more on managing containers, see working with Docker containers.

Q: Can Watchtower update containers from private registries?
A: Yes. Use REPO_USER and REPO_PASS for Docker Hub and GitHub Container Registry. For AWS ECR, configure the ECR credential helper or provide AWS credentials to the Watchtower container. Never store credentials in Compose files in version control; use secrets or an env_file that is not committed.

Q: How do I stop Watchtower from updating a specific container?
A: If you pass a list of container names to Watchtower, only those are monitored. To exclude a container when Watchtower is watching all containers, add the label com.centurylinklabs.watchtower.enable=false to that container. To only update selected containers, use --label-enable and add com.centurylinklabs.watchtower.enable=true only to the containers you want updated.

Q: Is it safe to run Watchtower in a production environment?
A: Watchtower automatically restarts containers when it applies updates, which causes brief downtime per container. It does not perform health checks or rollback. Use it in production only for low-criticality workloads where automatic restarts are acceptable. For production that requires change control, rollback, or zero-downtime, use a CI/CD-driven approach (e.g., Renovate plus your pipeline) instead.

Conclusion

Watchtower offers a simple and effective way to automate Docker container image updates on Ubuntu 22.04, with support for common registries, private authentication, and flexible notification options. It is ideal for homelabs, developer environments, and workloads where minimizing maintenance is a priority and occasional restarts are acceptable. While the project is no longer maintained, it continues to function well for many users, just be mindful of the security and operational trade-offs.

For larger or production deployments that demand zero-downtime, change control, rollback, or integration with CI/CD, consider alternatives like Renovate or managed deployment pipelines. Whatever your choice, always safeguard your secrets and monitor your containers to ensure your environment remains secure and up to date.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

Vinayak Baranwal
Vinayak Baranwal
Editor
Technical Writer II
See author profile

Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator. Technical Writer @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.

Still looking for an answer?

Was this helpful?


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.