Notify Stack: DIUN + ntfy
1. What? — Definition and context
Section titled “1. What? — Definition and context”The Notify Stack centralises two notification building blocks of the infrastructure:
| Component | Role | Image |
|---|---|---|
| DIUN | Docker image update detector | crazymax/diun |
| ntfy | Self-hosted push notification server | binwiederhier/ntfy |
Architecture diagram
Section titled “Architecture diagram”2. Why? — Stakes and motivations
Section titled “2. Why? — Stakes and motivations”Why DIUN over Watchtower or the Docker Hub UI?
Section titled “Why DIUN over Watchtower or the Docker Hub UI?”| Criterion | DIUN | Watchtower | Docker Hub UI |
|---|---|---|---|
| Detection without pull | Yes (compares digests) | No (pulls to verify) | Manual |
| Auto-update | No (notification only) | Yes (but blind) | No |
| File provider | Yes (custom base images) | No | No |
| Customisable webhook | Yes (Bearer auth) | No | No |
| Label-based exclusion | Yes | Yes | N/A |
Decision criteria:
- Decouple detection from update — Watchtower applies blindly, DIUN notifies and delegates the decision to an N8N workflow that can classify (critical / base / app) and request Telegram approval.
- Watch base images of custom Dockerfiles — The file provider lets you detect that a new version of
caddy:builderhas shipped even though no container runs that image directly. - No useless pull — DIUN compares digests via the registry API, without downloading the entire image.
Why ntfy over Pushover or Slack?
Section titled “Why ntfy over Pushover or Slack?”| Criterion | ntfy self-hosted | Pushover | Slack |
|---|---|---|---|
| Cost | Free (VPS) | Per-app fee | Free / paid |
| Sovereignty | Data on my own VPS | SaaS | SaaS |
| Simple API | curl HTTP | Proprietary API | Proprietary API |
| Auth | Token + user/pass | Token | OAuth + tokens |
| Multi-channel | Topics | Devices | Channels |
The goal is to have a notification channel separate from Telegram for monitoring alerts (Alertmanager, Grafana). Telegram is reserved for interactive flows (N8N workflows, approvals); ntfy is for silent mobile push.
3. How? — Technical implementation
Section titled “3. How? — Technical implementation”DIUN configuration
Section titled “DIUN configuration”The service runs without a UI, configured entirely through environment variables.
# notify-stack/docker-compose.yaml (DIUN excerpt)diun: image: crazymax/diun:latest volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - notify-diun-data:/data - ./diun/base-images.yml:/config/base-images.yml:ro environment: - DIUN_WATCH_WORKERS=10 - DIUN_WATCH_SCHEDULE=0 */6 * * * - DIUN_WATCH_FIRSTCHECKNOTIF=false - DIUN_PROVIDERS_DOCKER=true - DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true - DIUN_PROVIDERS_DOCKER_WATCHSTOPPED=false - DIUN_PROVIDERS_FILE_FILENAME=/config/base-images.yml - DIUN_NOTIF_DISCORD_WEBHOOKURL=${DISCORD_WEBHOOK_URL} - DIUN_NOTIF_WEBHOOK_ENDPOINT=${N8N_WEBHOOK_URL} - DIUN_NOTIF_WEBHOOK_HEADERS_AUTHORIZATION=Bearer ${DIUN_WEBHOOK_TOKEN}Two providers, two logics
Section titled “Two providers, two logics”| Provider | Source | Post-detection behaviour |
|---|---|---|
| docker | Running containers | Auto-update if category critical / base / Telegram approval if app |
| file | base-images.yml (FROM images of custom Dockerfiles) | Notification + manual rebuild or approval |
The base-images.yml file lists watched images that are not used directly by any container:
- name: docker.n8n.io/n8nio/n8n:latest # base of n8n-custom- name: caddy:builder # base of caddy-crowdsec- name: caddy:latest- name: node:20-slim # base of cli-ollama- name: ghcr.io/astral-sh/uv:latestntfy configuration
Section titled “ntfy configuration”ntfy: image: binwiederhier/ntfy:latest command: serve volumes: - ntfy-cache:/var/cache/ntfy - ntfy-data:/var/lib/ntfy - ./ntfy/server.yml:/etc/ntfy/server.yml:roThe server.yml enforces auth-default-access: deny-all: every topic must be published by an authenticated user. Two modes coexist:
| Mode | Use case |
|---|---|
| Bearer token | API calls from Alertmanager, N8N, scripts |
| User / password | Mobile app authentication |
# User management (from the VPS)docker exec ntfy ntfy user listdocker exec -e NTFY_PASSWORD="..." ntfy ntfy user add --role=admin guillaume
# API tokensdocker exec ntfy ntfy token add --label="alertmanager" alertmanager-botdocker exec ntfy ntfy token listTopics in use
Section titled “Topics in use”| Topic | Producers | Consumers |
|---|---|---|
alerts | Alertmanager, Notification Hub (fallback) | Mobile, Telegram |
updates | Docker DIUN workflow | Mobile |
monitoring | Grafana | Mobile |
Publishing a message:
# From the outside (HTTPS via Caddy)curl -H "Authorization: Bearer tk_xxx" \ -H "Title: Disk Alert" \ -H "Priority: high" \ -H "Tags: warning" \ -d "Disk usage above 80%" \ https://ntfy.guigpap.com/alerts
# From the inside (Docker network, from N8N)curl -H "Authorization: Bearer tk_xxx" \ -d "Message" \ http://ntfy:80/alertsIntegration with the Docker DIUN workflow
Section titled “Integration with the Docker DIUN workflow”DIUN does not apply any update: it posts an authenticated webhook to N8N, which makes every decision.
The image_policies table (managed in N8N Data Tables) defines the per-image behaviour. See the Docker Updates workflow article for the details of the sub-workflows and the auto-rebuild logic.
Operational commands
Section titled “Operational commands”# Stack managementdocker compose -f notify-stack/docker-compose.yaml up -ddocker compose -f notify-stack/docker-compose.yaml logs -f
# DIUN — force an immediate scandocker exec diun diun --run
# DIUN — list watched imagesdocker exec diun diun image list
# ntfy — publish from inside the container (test)docker exec ntfy ntfy publish mytopic "Test message"4. What if? — Outlook and limits
Section titled “4. What if? — Outlook and limits”Current limits
Section titled “Current limits”| Limit | Impact | Mitigation |
|---|---|---|
| DIUN does not push to ntfy directly | Every mobile notification flows through N8N | Add DIUN’s native ntfy notifier later |
| No DIUN UI | Visibility only via logs | The Docker DIUN workflow acts as the dashboard on the Telegram side |
| ntfy single-node | No HA | Auto-restart + container monitoring |
| Discord webhook quota | Spam possible if many images move | DIUN groups notifications per scan |
Evolution scenarios
Section titled “Evolution scenarios”If notification volume grows:
- Configure DIUN’s native ntfy notifier (direct output, without going through N8N for informational notifications).
- Enable DIUN bundling (group several detections into a single notification per scan).
If I want a fallback in case N8N is down:
- DIUN can write in parallel to Discord (already done) and ntfy (to add), guaranteeing the trail even if the N8N workflow crashes.
If I expose ntfy to other users:
- Create per-topic ACLs (separate read/write).
- Enable global rate limiting in
server.yml.
Metrics to watch
Section titled “Metrics to watch”| Metric | Source | Attention threshold |
|---|---|---|
| DIUN detections per scan | docker logs diun | Unusual spike = mass tag rotation by an upstream maintainer |
| DIUN webhook → N8N failures | DIUN logs + N8N DLQ | > 1 per scan = check auth or N8N availability |
| Excluded containers | docker exec diun diun image list | Verify regularly that nothing important is diun.enable=false by mistake |
Related pages
Section titled “Related pages”Infrastructure
Section titled “Infrastructure”- VPS Architecture — Big picture
- Security Stack — Caddy routes
ntfy.guigpap.com - Monitoring Stack — Alertmanager → ntfy
Workflows
Section titled “Workflows”- Docker Updates — Auto-update and approval logic
- Notification Hub — Centralised notification routing
Reference
Section titled “Reference”- Glossary — Webhook, Bearer token