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
| Feature | Diun | Watchtower | Manual |
|---|---|---|---|
| Detects new images | ✅ | ✅ | ❌ (you forget) |
| Auto-updates containers | ❌ (notify only) | ✅ | N/A |
| Notification channels | 18+ (Telegram, Discord, ntfy, etc.) | Email, Slack, Gotify, Shoutrrr | N/A |
| Watch specific tags | ✅ (regex filters) | Limited | N/A |
| Track all tags in a repo | ✅ | ❌ | ❌ |
| Private registry support | ✅ | ✅ | ✅ |
| Resource usage | ~15 MB RAM | ~30 MB RAM | Your 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
| Label | Description | Example |
|---|---|---|
diun.enable | Enable/disable watching | true |
diun.watch_repo | Watch all tags in the repo | true |
diun.notify_on | When to notify | new,update |
diun.sort_tags | Tag sort order | reverse, lexicographical |
diun.max_tags | Max tags to watch | 10 |
diun.include_tags | Regex for included tags | ^\d+\.\d+\.\d+$ |
diun.exclude_tags | Regex for excluded tags | `^(latest |
diun.platform | Override platform | linux/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:
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
- Verify the bot token with
curl https://api.telegram.org/bot<token>/getMe - Make sure you’ve sent a message to the bot first (bots can’t initiate)
- 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.
Related Guides
- Setting Up Watchtower: Auto-Update Docker Containers — the auto-update approach
- Docker Volume Management: Backups, Migration, and Best Practices — protect your data before updating
- Complete Guide to Docker Healthchecks and Restart Policies — keep containers running after updates
- Automating Docker Updates: Watchtower vs Diun vs Manual Strategies — the full comparison (coming soon)