Self-Hosting Diun: Docker Image Update Notifications

You’re running twenty Docker containers. One of them just got a critical security patch upstream. How long until you notice?

If you’re using Watchtower, the answer is “automatically — it’ll update itself.” But automatic updates aren’t always what you want. Maybe you run a database that needs careful migration steps. Maybe you’ve been burned by a breaking change at 3 AM. Maybe you just want to know before you act.

That’s where Diun (Docker Image Update Notifier) comes in. It watches your container images for new versions and tells you about them — via Telegram, Discord, Slack, email, ntfy, webhooks, and a dozen other channels. No automatic restarts, no surprise breakage. Just a heads-up that something new is available.


Diun vs Watchtower vs Manual Checking

FeatureDiunWatchtowerManual
Detects new images❌ (you forget)
Auto-updates containers❌ (notify only)N/A
Notification channels18+ (Telegram, Discord, ntfy, etc.)Email, Slack, Gotify, ShoutrrrN/A
Watch specific tags✅ (regex filters)LimitedN/A
Track all tags in a repo
Private registry support
Resource usage~15 MB RAM~30 MB RAMYour brain
Kubernetes/Swarm/Nomad

Use Diun when you want visibility without automation — you’ll decide what to update and when. Use Watchtower when you trust upstream releases and want hands-off updates. Many people run both: Watchtower for low-risk containers, Diun for everything else.


Prerequisites

  • Docker and Docker Compose installed
  • A notification service (Telegram bot, Discord webhook, ntfy topic, email server, etc.)
  • 5 minutes

Quick Start with Docker Compose

Create a directory for Diun:

mkdir -p ~/docker/diun && cd ~/docker/diun

Create your docker-compose.yml:

services:
  diun:
    image: crazymax/diun:latest
    container_name: diun
    command: serve
    volumes:
      - "./data:/data"
      - "/var/run/docker.sock:/var/run/docker.sock"
    environment:
      - "TZ=America/New_York"
      - "DIUN_WATCH_WORKERS=10"
      - "DIUN_WATCH_SCHEDULE=0 */6 * * *"
      - "DIUN_WATCH_JITTER=30s"
      - "DIUN_WATCH_FIRSTCHECKNOTIF=false"
      - "DIUN_PROVIDERS_DOCKER=true"
    restart: unless-stopped

This is the minimum viable setup. Diun will scan all labeled containers every 6 hours. But without a notification backend, it’s just logging to stdout. Let’s fix that.


Adding Notifications

Diun supports 18+ notification backends. Here are the most popular ones.

Telegram

The most common choice for self-hosters. Create a bot via @BotFather, get your chat ID, and add these environment variables:

environment:
  # ... existing vars ...
  - "DIUN_NOTIF_TELEGRAM_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
  - "DIUN_NOTIF_TELEGRAM_CHATIDS=your_chat_id"
  - "DIUN_NOTIF_TELEGRAM_TEMPLATEBODY=Docker tag {{ .Entry.Image }} {{ if (eq .Entry.Status \"new\") }}is available{{ else }}has been updated{{ end }} on {{ .Entry.Image.Domain }}."

Discord

Create a webhook in your Discord server (Server Settings → Integrations → Webhooks):

environment:
  - "DIUN_NOTIF_DISCORD_WEBHOOKURL=https://discord.com/api/webhooks/your/webhook"
  - "DIUN_NOTIF_DISCORD_MENTIONS=@everyone"

ntfy

If you’re running ntfy (self-hosted or the public instance):

environment:
  - "DIUN_NOTIF_NTFY_ENDPOINT=https://ntfy.sh"
  - "DIUN_NOTIF_NTFY_TOPIC=my-diun-notifications"
  - "DIUN_NOTIF_NTFY_PRIORITY=3"

Email (SMTP)

environment:
  - "DIUN_NOTIF_MAIL_HOST=smtp.gmail.com"
  - "DIUN_NOTIF_MAIL_PORT=587"
  - "DIUN_NOTIF_MAIL_SSL=false"
  - "DIUN_NOTIF_MAIL_STARTTLS=true"
  - "[email protected]"
  - "DIUN_NOTIF_MAIL_PASSWORD=your_app_password"
  - "[email protected]"
  - "[email protected]"

Webhook (Custom)

Send JSON payloads to any HTTP endpoint — perfect for Home Assistant, n8n, or custom automation:

environment:
  - "DIUN_NOTIF_WEBHOOK_ENDPOINT=http://your-service:8080/diun"
  - "DIUN_NOTIF_WEBHOOK_METHOD=POST"
  - "DIUN_NOTIF_WEBHOOK_HEADERS_CONTENT-TYPE=application/json"

You can use multiple notification backends simultaneously. Just add the environment variables for each one.


Selecting Which Containers to Watch

By default, Diun only watches containers with the diun.enable=true label. Add it to any service:

services:
  nextcloud:
    image: nextcloud:latest
    labels:
      - "diun.enable=true"

  jellyfin:
    image: jellyfin/jellyfin:latest
    labels:
      - "diun.enable=true"
      - "diun.watch_repo=true"

Watch All Containers by Default

If you’d rather opt-out than opt-in:

environment:
  - "DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true"

Then exclude specific containers with diun.enable=false.

Docker Labels Reference

LabelDescriptionExample
diun.enableEnable/disable watchingtrue
diun.watch_repoWatch all tags in the repotrue
diun.notify_onWhen to notifynew,update
diun.sort_tagsTag sort orderreverse, lexicographical
diun.max_tagsMax tags to watch10
diun.include_tagsRegex for included tags^\d+\.\d+\.\d+$
diun.exclude_tagsRegex for excluded tags`^(latest
diun.platformOverride platformlinux/arm64

Tag Filtering Examples

Only watch semantic version tags (skip latest, dev, nightly):

labels:
  - "diun.enable=true"
  - "diun.include_tags=^\\d+\\.\\d+\\.\\d+$$"

Watch the repo but limit to the 5 most recent tags:

labels:
  - "diun.enable=true"
  - "diun.watch_repo=true"
  - "diun.max_tags=5"
  - "diun.sort_tags=reverse"

Using a Configuration File

For complex setups, a YAML config file is cleaner than dozens of environment variables. Mount it as diun.yml:

# docker-compose.yml
services:
  diun:
    image: crazymax/diun:latest
    command: serve
    volumes:
      - "./data:/data"
      - "./diun.yml:/diun.yml:ro"
      - "/var/run/docker.sock:/var/run/docker.sock"
    environment:
      - "TZ=America/New_York"
    restart: unless-stopped
# diun.yml
watch:
  workers: 10
  schedule: "0 */6 * * *"
  jitter: 30s
  firstCheckNotif: false

defaults:
  watchRepo: false
  notifyOn:
    - new
    - update
  sortTags: reverse
  maxTags: 10

providers:
  docker:
    watchByDefault: false
    watchStopped: true

notif:
  telegram:
    token: "your-bot-token"
    chatIDs:
      - 123456789
  discord:
    webhookURL: "https://discord.com/api/webhooks/..."

Watching Non-Docker Images (File Provider)

Diun can also watch images that aren’t running as containers. Use the file provider to track images you’re interested in:

# diun.yml
providers:
  file:
    directory: /data/watch-list
# /data/watch-list/images.yml
- name: ghcr.io/immich-app/immich-server
  watch_repo: true
  max_tags: 5
  include_tags:
    - ^\d+\.\d+\.\d+$
- name: postgres
  include_tags:
    - ^16
- name: redis
  include_tags:
    - ^7

This is great for tracking upstream projects you plan to deploy, or images used in other orchestration systems.


Private Registry Authentication

For images hosted on private registries (GHCR, GitLab, self-hosted):

# diun.yml
regopts:
  - name: "ghcr.io"
    selector: image
    username: your-github-username
    password: ghp_your_personal_access_token
  - name: "registry.gitlab.com"
    selector: image
    username: your-gitlab-username
    password: your-gitlab-token
  - name: "my-registry.example.com"
    selector: image
    username: admin
    password: secretpassword
    insecureTLS: true

For Docker Hub rate limits, add your credentials too:

regopts:
  - name: "docker.io"
    selector: image
    username: your-dockerhub-username
    password: your-dockerhub-token

Reverse Proxy Setup

Caddy

Diun doesn’t have a web UI, so you typically don’t need a reverse proxy. But if you’re using the webhook notification inbound (receiving webhooks), here’s how:

d}iun.ryeovuerrdsoem_apirno.xcyomdi{un:8080

Checking Diun Status

Diun exposes no HTTP interface by default. Check it via Docker logs:

# Live logs
docker logs -f diun

# Last scan results
docker logs diun 2>&1 | grep -E "(New|Unchanged|image)"

Backup and Restore

Diun stores image manifests in a bbolt database at /data/diun.db. Back it up to avoid re-triggering “new image” notifications after a rebuild:

# Backup
docker compose stop diun
tar czf diun-backup-$(date +%Y%m%d).tar.gz data/
docker compose start diun

# Restore
docker compose stop diun
tar xzf diun-backup-20260321.tar.gz
docker compose start diun

If you’re using Kopia or Duplicati, include the data/ directory in your backup paths.


Troubleshooting

No notifications on first run

By default, firstCheckNotif is false — Diun won’t notify you about images it discovers on its first scan (it’s just building its database). Set DIUN_WATCH_FIRSTCHECKNOTIF=true if you want initial notifications.

“No image found” in logs

The container doesn’t have the diun.enable=true label, or watchByDefault is false. Check your labels:

docker inspect your-container | jq '.[0].Config.Labels'

Telegram notifications not arriving

  1. Verify the bot token with curl https://api.telegram.org/bot<token>/getMe
  2. Make sure you’ve sent a message to the bot first (bots can’t initiate)
  3. Get your chat ID: curl https://api.telegram.org/bot<token>/getUpdates

Rate limiting from Docker Hub

Docker Hub limits anonymous pulls to 100/6h and authenticated to 200/6h. If you’re watching many images, add Docker Hub credentials in regopts and increase the scan interval.

High CPU during scans

Reduce the number of workers:

environment:
  - "DIUN_WATCH_WORKERS=5"

And increase jitter to spread requests:

environment:
  - "DIUN_WATCH_JITTER=60s"

Diun not detecting my ARM images

Diun defaults to the host platform. For multi-arch images, override per container:

labels:
  - "diun.platform=linux/arm64"

Power User Tips

Combine Diun with Watchtower

Run both. Use Watchtower for safe, auto-updatable containers and Diun for everything else:

services:
  # Watchtower auto-updates low-risk containers
  watchtower:
    image: containrrr/watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WATCHTOWER_LABEL_ENABLE=true
      - WATCHTOWER_SCHEDULE=0 4 * * *

  # Diun notifies about everything else
  diun:
    image: crazymax/diun:latest
    command: serve
    volumes:
      - "./data:/data"
      - "/var/run/docker.sock:/var/run/docker.sock"
    environment:
      - "DIUN_WATCH_SCHEDULE=0 */6 * * *"
      - "DIUN_PROVIDERS_DOCKER=true"

Label containers accordingly — com.centurylinklabs.watchtower.enable=true for auto-update, diun.enable=true for notify-only.

Custom notification templates

Diun uses Go templates for notification messages. Customize them to include exactly what you need:

environment:
  - |
    DIUN_NOTIF_TELEGRAM_TEMPLATEBODY=
    🐳 *{{ .Entry.Image }}*
    Status: {{ if (eq .Entry.Status "new") }}🆕 New{{ else }}🔄 Updated{{ end }}
    Platform: {{ .Entry.Image.Platform }}
    Digest: `{{ .Entry.Manifest.Digest }}`

Healthchecks.io integration

Monitor that Diun itself is running:

# diun.yml
watch:
  schedule: "0 */6 * * *"
  healthchecks:
    baseURL: "https://hc-ping.com"
    uuid: "your-healthchecks-uuid"

Run on startup

Force a scan immediately when Diun starts (useful after maintenance):

environment:
  - "DIUN_WATCH_RUNONSTARTUP=true"

Wrapping Up

Diun fills the gap between “I’ll manually check Docker Hub every week” (you won’t) and “let Watchtower update everything automatically” (sometimes risky). It gives you awareness — you know when updates are available, and you decide what to do about them.

For most self-hosters, the sweet spot is running both: Watchtower for trusted, stateless containers (reverse proxies, static sites, simple apps) and Diun for everything with data, databases, or breaking-change potential.

Set it up, configure your preferred notification channel, label your containers, and stop wondering whether your images are up to date.