A lightweight, event-driven Docker monitor that listens to the Docker socket on a host and fans out container events to multiple downstream notifiers.
It was built to solve two specific problems in a homelab:
- Keep a Technitium DNS server's records in sync with what's actually running on each Docker host.
- Push container metadata (URLs, health-check flags, grouping, icons) to a
self-hosted dashboard (Service Tracker Dashboard)
so the dashboard config is driven by
docker-composelabels rather than hand-edited.
Each container opts in to notifiers via labels, so you can run this on every Docker host without it touching things you didn't ask it to touch.
- What It Does
- Environment Variables
- Container Labels
- Interpreters
- Docker Compose Example
- How It Works
- Building Locally
docker-api-notifier connects to the Docker socket on its host and:
- Scans every running container at startup ("boot" pass).
- Subscribes to the Docker event stream for ongoing changes (
start,stop,die,pause,unpause,destroy,kill,update). - Re-scans every running container on a periodic interval as a self-healing measure (default every 60 seconds). This loop only runs when STD is configured — it exists to re-report containers to STD, and does nothing for DNS-only deployments.
For each event, it reads the container's labels and dispatches to whichever
notifiers the container has opted in to via dockernotifier.notifiers.
Supported notifiers today:
- Technitium DNS — adds/updates a CNAME record on container start.
- Service Tracker Dashboard (STD) — POSTs container metadata to STD's register endpoint.
The notifier is a generic event fan-out. STD is one consumer; new notifier targets can be added without touching the core event loop.
| Variable | Required | Default | Description |
|---|---|---|---|
TZ |
No | UTC |
Timezone for log timestamps. |
STD_REFRESH_SECONDS |
No | 60 |
Periodic re-scan interval in seconds. |
NOTIFIER_LOG_TO_STDOUT |
No | 1 |
Set to 0 to silence console output. Logs still go to /config/notifier.log. Replaces the per-notifier DNS_LOG_TO_STDOUT and STD_LOG_TO_STDOUT vars, which are no longer recognized. |
HOST_NAME_OVERRIDE |
No | host hostname | Overrides the host name used as the DNS CNAME target (<host>.<dockerdomain>). If unset, the notifier reads the host's hostname from the /etc/hostname mount (falling back to the container's own hostname). Set this if running in WSL or other environments where the host has a different name than your DNS entries expect. |
| Variable | Required | Description |
|---|---|---|
DNS_SERVER_URL |
Yes (for DNS) | Full URL to the Technitium add-record endpoint. |
DNS_SERVER_API_TOKEN |
Yes (for DNS) | API token for the DNS server. |
DNS_SERVER_TYPE |
No | Optional descriptor (informational only). |
Requires STD v0.5.0 or later. Starting in notifier v0.3.0, the notifier posts to STD's
/api/v1/registerendpoint using STD's canonical schema. Earlier STD versions do not expose that endpoint and will return 404.Notifier v0.4.0 requires STD v0.6.0 or later. Starting in v0.4.0 the notifier emits
networks,exposed_ports,published_ports, andexposure_observationson every STD payload. STD v0.5.x's strict pydantic validator rejects unknown keys and will return 422 for these payloads — upgrade STD first.
| Variable | Required | Default | Description |
|---|---|---|---|
STD_URL |
Yes (for STD) | — | Base URL of the STD instance, e.g. http://std.example.com:8815. |
STD_API_TOKEN |
Yes (for STD) | — | Bearer token configured on the STD side. |
STD_REPORT_ALL_CONTAINERS |
No | false |
When truthy (true, 1, yes — case-insensitive), report every running container on this host to STD regardless of whether it has the dockernotifier.notifiers=service-tracker-dashboard opt-in label. Default off preserves per-container opt-in behavior. Only affects STD — the DNS notifier still requires explicit per-container opt-in via labels. Unrecognized values log a warning at startup and are treated as off. |
INTERPRETER_RELOAD_ON_EACH_EVENT |
No | false |
Debug-only. When truthy, re-reads YAML interpreters from disk on every dispatch instead of once at startup. Use while iterating on a new YAML; do not leave on in production. |
If a notifier's required env vars are missing, that notifier no-ops —
the container won't fail to start. This is intentional so you can run
the same image with only DNS, only STD, or both. When STD's env vars
(STD_URL / STD_API_TOKEN) are absent the notifier logs a single
line at startup noting STD is disabled, skips the periodic refresh
loop, and does not attempt STD dispatch on any event.
You opt a container in to notification by adding labels to it. None of these labels are required to run the notifier itself; they're read off the containers being watched.
| Label | Description |
|---|---|
dockernotifier.notifiers |
Comma-separated list of notifiers to run for this container. Valid values: dns, service-tracker-dashboard. |
All three are required for the DNS notifier to act on a container.
| Label | Description |
|---|---|
dockernotifier.dns.containerhostname |
Hostname portion of the record (e.g. sonarr). |
dockernotifier.dns.containerzone |
Zone/domain (e.g. home.local). |
dockernotifier.dns.dockerdomain |
Docker host domain (e.g. docker). The CNAME will point at <host>.<dockerdomain>. |
All STD labels are optional. Anything you set gets forwarded to STD; STD applies its own defaults for anything you don't.
| Label | Description |
|---|---|
dockernotifier.std.internalurl |
Internal service URL. |
dockernotifier.std.externalurl |
Public/external URL. |
dockernotifier.std.internal.health |
true/false — enable internal health check. |
dockernotifier.std.external.health |
true/false — enable external health check. |
dockernotifier.std.group |
Group label for dashboard organization. |
dockernotifier.std.icon |
Icon filename (e.g. sonarr.svg). |
dockernotifier.std.sort.priority |
Numeric sort order within a group. |
Note on label naming. Container label keys (the
dockernotifier.std.*keys you set on watched containers) are unchanged. Internally, notifier v0.3.0 translates them to STD's canonical wire-format keys (group_name,image_icon,internal_health_check_enabled,sort_priority, ...) before posting to STD's/api/v1/registerendpoint. Boolean and integer coercion happens at the same boundary, so the values STD receives are actualbool/intrather than strings.
Network and port data. As of notifier v0.4.0, every STD payload also carries
networks,exposed_ports, andpublished_portsread straight from the Docker API. No new labels or env vars are required to enable this — it is automatic for every container reported to STD. Requires STD v0.6.0+.
New in v0.4.0. Requires STD v0.6.0 or later. STD v0.5.x's strict validator will reject payloads carrying
exposure_observations.
Interpreters are small YAML files that teach the notifier how to read
labels written by third-party tools (Traefik, Dockflare, Caddy, ...)
and forward them to STD as structured exposure observations. The
goal is to stop operators from having to duplicate hostnames into
dockernotifier.std.internalurl when the same fact is already
encoded in their Traefik/Dockflare labels.
Two interpreters live inside the container image at
/app/interpreters/builtin/:
traefik.yml— readstraefik.http.routers.<router>.rule(hostname),.tls, and.entrypoints. Emits one observation per router on the container.dockflare.yml— fires whendockflare.enable=true; readsdockflare.hostnameand optional Access policy labels. Emits a single observation withtls: true(Cloudflare Tunnel implies HTTPS).
Both fire automatically for any container reported to STD. There is no opt-in label — if the labels are there, the interpreter reads them.
Mount a directory of YAML files into the container at
/app/interpreters/user/:
volumes:
- ./my-interpreters:/app/interpreters/user:roUser files load alongside builtins. A user file whose name: matches
a builtin overrides the builtin — drop in a tweaked traefik.yml
without rebuilding the image.
Every interpreter has three sections: match (which containers
fire), extract (which labels to read), emit (what to send to
STD). See docs/PRD.md §11 for the full reference,
or docs/community-interpreters/template.yml for an annotated
skeleton.
docs/community-interpreters/ collects contributed YAMLs for tools
the maintainer doesn't necessarily run. Examples there may or may
not match your environment — read before mounting. PRs welcome.
The notifier sends exposure_observations as a list when any
interpreter is loaded, even if no interpreter matched (empty list
tells STD to clear existing exposure rows for the container). If no
interpreters are loaded at all, the field is omitted, which STD
treats as "no update" — existing exposure rows are preserved.
services:
docker-api-notifier:
image: crzykidd/docker-api-notifier:latest
container_name: docker-api-notifier
environment:
- DNS_SERVER_TYPE=Technitium
- DNS_SERVER_URL=http://dns.example.com:5380/api/zones/records/add
- DNS_SERVER_API_TOKEN=TOKENFROMDNSSERVER
- STD_URL=http://std.example.com:8815
- STD_API_TOKEN=TOKENFROMSTDSERVER
- TZ=America/Los_Angeles
- STD_REFRESH_SECONDS=60
# Override the host name used as the DNS CNAME target (<host>.<dockerdomain>).
# If unset, the notifier uses the host's hostname (read from the
# /etc/hostname mount below). Set this if running in WSL or other
# environments where the host has a different name than your DNS
# entries expect.
# - HOST_NAME_OVERRIDE=wsl-host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/hostname:/etc/host_hostname:ro
- /var/docker/docker-api-notifier:/config
# Optional — drop your own interpreter YAMLs in here.
# - /etc/docker-api-notifier/interpreters:/app/interpreters/user:ro
restart: unless-stoppedVolumes:
/var/run/docker.sock— required, this is how the notifier reads events./etc/hostnamemounted as/etc/host_hostname— used so the notifier reports the host's hostname, not the container's, when posting to downstream notifiers./config— log file lives here (notifier.log, rotated at 10 MB)./app/interpreters/user— optional. Mount a directory of operator YAMLs here to extend or override the built-in interpreters (Traefik, Dockflare). See Interpreters above.
┌────────────────────┐
│ Docker socket │
│ (events stream) │
└─────────┬──────────┘
│
┌───────▼────────┐
│ main.py │ ← reads events,
│ event loop │ enriches with labels,
└───────┬────────┘ decides who to call
│
┌───────────────────┼────────────────────┐
│ │
┌────────▼─────────┐ ┌───────────▼──────────┐
│ technitium_dns │ │ service_tracker_ │
│ .register() │ │ dashboard.register() │
└──────────────────┘ └──────────────────────┘
│ │
▼ ▼
Technitium API STD /api/v1/register
- Container events arrive from the Docker socket and are filtered against a whitelist of actions the notifier cares about.
- Per event, container labels are read; only containers with
dockernotifier.notifiersset are processed. - Each enabled notifier is a Python module under
notifiers/exposing aregister(...)function. Adding a new notifier target is a matter of dropping a new module and wiring it into the dispatch inmain.py. - Both notifiers share a single retry-with-backoff policy
(
tenacity, exposed via thewith_retrydecorator inretry.py) for transient network failures: 3 attempts, exponential backoff 2s/4s/8s capped at 10s, retries onrequests.RequestException. The DNS notifier also callsraise_for_status()so HTTP 4xx/5xx responses from Technitium trigger retries instead of being logged as successes.
git clone https://github.com/crzykidd/docker-api-notifier.git
cd docker-api-notifier
docker build -t docker-api-notifier:dev .Run pointed at your dev Docker socket and a scratch config dir.
:latestfollows themainbranch — CI-verified pre-release.:devfollows thedevbranch — work in progress.:sha-<short>is published for every push for exact pinning.- Semver-tagged images (
:0.3.0,:0) are published from GitHub Releases.
Branch protection: PRs into main must pass the build check; force-push
and branch deletion are blocked. Work happens on dev, opens a PR to
main, and merges only when CI is green. Release tags are cut from the
GitHub Releases UI on main.
MIT — see LICENSE.