Introduction

This all started when my Komari monitoring probe notified me of a minor version update. To apply it, I had to go into the Docker host, pull the latest image, stop the container, remove the container, and then run a new one.

Of course, you could put the configuration into a docker-compose.yml file, and each time you’d just run:

docker compose pull
docker compose up -d

However, you still inevitably have to log into the server and run commands to perform the update. Therefore, here are a few ways to achieve automatic Docker container updates. This will cover scenarios both with and without a docker-compose.yml file.

Watchtower

Watchtower periodically checks if there is a new version of the image used by your running containers. If there is, it automatically pulls the new image and restarts the container using the original container’s configuration.

Launching Watchtower (To update only labeled containers, avoiding updates to others you don’t want to change)

docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e WATCHTOWER_POLL_INTERVAL=300 \
  containrrr/watchtower \
  --label-enable \
  --cleanup
  • WATCHTOWER_POLL_INTERVAL=300: Checks every 5 minutes.
  • --cleanup: Deletes the old image after an update to save space.
  • You can also use a crontab-style schedule: -e WATCHTOWER_SCHEDULE="0 0 4 * * *" (checks every day at 4 AM). You can calculate your desired interval and set it accordingly.

Add a label to the container you want to auto-update and (re)start it—using Komari as an example

If you already have a Komari container running, you need to stop and remove it first. After that, restart it with the label applied:

docker run -d \
  --label=com.centurylinklabs.watchtower.enable=true \
  -p 25774:25774 \
  -v $(pwd)/data:/app/data \
  --name komari \
  ghcr.io/komari-monitor/komari:latest

After this, Watchtower will seamlessly update komari, preserving the original port, volumes, environment variables, etc.

Drawbacks

  1. Reliance on image tag strategy: It can only detect updates to an existing tag, such as latest or 1.2. If the image author only publishes new tags (e.g., from 1.2.31.2.4) and doesn’t overwrite the old tag, Watchtower will not do anything.
  2. Lack of a rollback mechanism: By default, Watchtower does not automatically revert to the old version if an update fails. If an upstream repository pushes a broken image, your container could crash.
  3. Dependency on docker.sock: Watchtower must mount /var/run/docker.sock, which effectively gives it root-level privileges. If Watchtower or one of its dependency images is compromised, the entire host machine could be at risk.

Docker Compose + systemd timer

This method involves writing the “original configuration” into a docker-compose.yml file and then using a systemd timer to periodically execute pull and up.

Create a Compose file (example for Komari)

# /srv/komari/docker-compose.yml
services:
  komari:
    image: ghcr.io/komari-monitor/komari:latest
    container_name: komari
    ports:
      - "25774:25774"
    volumes:
      - /srv/komari/data:/app/data
    restart: unless-stopped

Run and verify it first

mkdir -p /srv/komari/data
cd /srv/komari
docker compose up -d

Create systemd service and timer for scheduled updates:

# /etc/systemd/system/komari-update.service
[Unit]
Description=Update Komari container via docker compose
After=network-online.target docker.service
Wants=network-online.target

[Service]
Type=oneshot
WorkingDirectory=/srv/komari
ExecStart=/usr/bin/docker compose pull --quiet
ExecStart=/usr/bin/docker compose up -d --remove-orphans
# Optional: Clean up dangling images
ExecStart=/usr/bin/docker image prune -f

[Install]
WantedBy=multi-user.target
# /etc/systemd/system/komari-update.timer
[Unit]
Description=Daily update check for Komari

[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=true
Unit=komari-update.service

[Install]
WantedBy=timers.target

Enable the timer

systemctl daemon-reload
systemctl enable --now komari-update.timer
systemctl start komari-update.service

This way, it will automatically pull the new image and restart the container using the same compose configuration every day at 04:00. You can also change the schedule by modifying the OnCalendar=*-*-* 04:00:00 setting in komari-update.timer.

Pure Shell Script + systemd (No Compose, but you must save the “original command”)

If you simply start your containers with docker run, you can still automate updates. The key is to solidify the original run command into a script that handles pulling, comparing, and rebuilding.

Example Script

# /usr/local/bin/update-komari
#!/usr/bin/env bash
set -euo pipefail

NAME="komari"
IMAGE="ghcr.io/komari-monitor/komari:latest"

echo "[*] Pulling $IMAGE..."
docker pull "$IMAGE" >/dev/null

current_id="$(docker inspect -f '{{.Image}}' "$NAME" 2>/dev/null || true)"
new_id="$(docker inspect -f '{{.Id}}' "$IMAGE")"

if [[ -n "$current_id" && "$current_id" == "$new_id" ]]; then
  echo "[=] Already up-to-date."
  exit 0
fi

echo "[*] Recreating container $NAME..."
docker rm -f "$NAME" 2>/dev/null || true

# Place and modify your original docker run command here
docker run -d \
  -p 25774:25774 \
  -v /srv/komari/data:/app/data \
  --name "$NAME" \
  "$IMAGE"

echo "[+] $NAME updated to $(docker inspect -f '{{.Image}}' "$NAME")"
# Optional: Clean up old images
docker image prune -f >/dev/null || true

Set Permissions and Place in PATH

install -m 0755 /path/to/your/script/update-komari /usr/local/bin/update-komari

Configure Scheduled Updates (using the same .service/.timer method as above)

# /etc/systemd/system/update-komari.service
[Unit]
Description=Update komari container
After=docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/update-komari
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/update-komari.timer
[Unit]
Description=Nightly komari update
[Timer]
OnCalendar=*-*-* 04:30:00
Persistent=true
[Install]
WantedBy=timers.target

Recommendations

  • For a “set it and forget it” solution: Use Watchtower. Remember to apply labels to the containers you want to auto-update; unlabeled containers will be ignored.
  • For an auditable and version-controlled configuration: Use Compose + systemd. This keeps your configuration neatly in a docker-compose.yml file.
  • If you only use docker run and don’t want to migrate yet: Use the script + systemd method, but be sure to hardcode the “original run command” in the script to avoid losing it.

Using Watchtower to Update Automatically

Author

Shayne Wong

Publish Date

10 - 08 - 2025

License

Shayne Wong

Avatar
Shayne Wong

All time is no time when it is past.