feat(diun): add diun template reading the Docker API through a proxy

Diun needs the Docker API to enumerate containers and inspect each one's
image. Mounting the socket into it directly is host-root-equivalent, and :ro on
a socket mount is cosmetic, so the socket goes into a docker-socket-proxy
sidecar and Diun reaches it at tcp://dockerproxy:2375. POST is revoked there,
so container create and exec return 403.

CONTAINERS and IMAGES are both required: with CONTAINERS alone the provider
loads and enumerates containers, then every ImageInspect returns 403 and
nothing is analysed. Verified against a live watch cycle — 25 images analysed,
no errors.

Pin crazymax/diun:4.33 rather than :latest, since this is the service whose job
is to talk to the daemon.
This commit is contained in:
tiennm99 committed 2026-09-19 17:18:31 +07:00
1 parent c2502d5f44
commit 24cefe80a2
4 files changed
+110

No files matched your search

+1
View File
@@ -56,6 +56,7 @@ Each links to its own README for variables, ports, and storage.
| [alloy](alloy/README.md) | Grafana Alloy shipping host and Docker telemetry to Grafana Cloud |
| [code-server](code-server/README.md) | VS Code in the browser, as a remote dev box |
| [couchbase](couchbase/README.md) | Couchbase Server |
| [diun](diun/README.md) | Image-update notifier, reading the Docker API through a read-only proxy |
| [gitea-mirror](gitea-mirror/README.md) | Gitea + PostgreSQL + gitea-mirror, mirroring GitHub repos |
| [opencode-web](opencode-web/README.md) | opencode coding agent, served as a browser UI |
| [openhands](openhands/README.md) | OpenHands coding agent, running each session in a container it spawns |
+9
View File
@@ -0,0 +1,9 @@
DIUN_NOTIF_TELEGRAM_TOKEN=
DIUN_NOTIF_TELEGRAM_CHATIDS=
DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true
DIUN_WATCH_SCHEDULE=0 */6 * * *
DIUN_WATCH_WORKERS=10
DIUN_WATCH_JITTER=30s
TZ=UTC
LOG_LEVEL=info
LOG_JSON=false
+62
View File
@@ -0,0 +1,62 @@
# diun
[Diun](https://crazymax.dev/diun/) watches the images of every running
container and sends a Telegram message when one has an update. It only
notifies — it never pulls or restarts anything.
Two containers: `diun` itself, and `dockerproxy`, which hands it a read-only
slice of the Docker API.
## Docker API access
Diun needs the Docker API to enumerate containers and read the image reference
each one runs. Mounting `/var/run/docker.sock` into it directly would be
host-root-equivalent — the API has no read/write split, so anything that can
talk to the socket can create a privileged container that mounts `/`. Adding
`:ro` to the mount does not help: it stops the socket *file* being replaced, not
the API being used.
So the socket is mounted into
[tecnativa/docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy)
instead, and Diun reaches it over the compose network at
`tcp://dockerproxy:2375`. `POST` is revoked by default in that image, so
container create, `exec`, start and kill return 403. A compromised Diun image
can no longer become root on the host — which matters because Diun is the one
service here whose whole job is to talk to the daemon.
Exactly two API sections are granted, both verified against a live watch cycle:
| Variable | Why |
| --- | --- |
| `CONTAINERS` | enumerate running containers |
| `IMAGES` | `ImageInspect` on each container's image — without it every image logs `403 Forbidden` and nothing is analysed |
`INFO`, `NETWORKS`, `VOLUMES` and the rest stay revoked; a watch cycle runs
clean without them.
## Environment
`DIUN_NOTIF_TELEGRAM_TOKEN` and `DIUN_NOTIF_TELEGRAM_CHATIDS` are required and
fail fast if unset. Everything else has a working default.
| Variable | Default | Purpose |
| --- | --- | --- |
| `DIUN_NOTIF_TELEGRAM_TOKEN` | — | Bot token from @BotFather |
| `DIUN_NOTIF_TELEGRAM_CHATIDS` | — | Comma-separated chat ids to notify |
| `DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT` | `true` | Watch every container without per-container labels |
| `DIUN_WATCH_SCHEDULE` | `0 */6 * * *` | Cron for the watch cycle |
| `DIUN_WATCH_WORKERS` | `10` | Parallel registry lookups |
| `DIUN_WATCH_JITTER` | `30s` | Random delay before each job |
| `TZ` | `UTC` | Timezone for the schedule and log timestamps |
| `LOG_LEVEL` / `LOG_JSON` | `info` / `false` | Logging |
State lives in the `diun-data` volume (`DIUN_DB_PATH=/data/diun.db`). Diun
notifies on first sight of an image, so a fresh volume produces one round of
notifications for everything currently running.
## Version pinning
`crazymax/diun:4.33` rather than `:latest`. Diun holds Docker API access, so an
unreviewed image change is the highest-leverage supply-chain step on the host;
the proxy bounds what a bad image could do, and the pin means an image only
changes when this file does.
+38
View File
@@ -0,0 +1,38 @@
# Required env vars (set in Coolify/Dokploy, or a sibling .env):
# DIUN_NOTIF_TELEGRAM_TOKEN, DIUN_NOTIF_TELEGRAM_CHATIDS
services:
diun:
image: crazymax/diun:4.33
command: serve
environment:
DIUN_PROVIDERS_DOCKER: "true"
DIUN_PROVIDERS_DOCKER_ENDPOINT: tcp://dockerproxy:2375
DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT: "${DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT:-true}"
DIUN_NOTIF_TELEGRAM_TOKEN: ${DIUN_NOTIF_TELEGRAM_TOKEN:?required}
DIUN_NOTIF_TELEGRAM_CHATIDS: ${DIUN_NOTIF_TELEGRAM_CHATIDS:?required}
DIUN_DB_PATH: /data/diun.db
DIUN_WATCH_SCHEDULE: "${DIUN_WATCH_SCHEDULE:-0 */6 * * *}"
DIUN_WATCH_WORKERS: "${DIUN_WATCH_WORKERS:-10}"
DIUN_WATCH_JITTER: "${DIUN_WATCH_JITTER:-30s}"
TZ: "${TZ:-UTC}"
LOG_LEVEL: "${LOG_LEVEL:-info}"
LOG_JSON: "${LOG_JSON:-false}"
volumes:
- diun-data:/data
depends_on:
- dockerproxy
# Read-only slice of the Docker API. POST is revoked by default in this image.
dockerproxy:
image: tecnativa/docker-socket-proxy:v0.5.0
environment:
CONTAINERS: 1
IMAGES: 1
security_opt:
- no-new-privileges:true
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
volumes:
diun-data: