Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.0] — 2026-05-14

### Added
- **YAML interpreter mechanism (v0.4.0).** The notifier now reads small
YAML files that teach it how to interpret labels written by
third-party tools (Traefik, Dockflare, ...) and forward them to STD
as structured exposure observations on a new
`exposure_observations` field. Two interpreters ship baked into the
image (`traefik.yml`, `dockflare.yml`) and fire automatically for
any container reported to STD — no opt-in label required. Operators
who want to extend or override the built-ins mount a directory of
their own YAMLs at `/app/interpreters/user/`; a user file whose
`name:` matches a builtin overrides the builtin. Bad YAMLs log a
warning and are skipped; the notifier continues with whatever
loaded successfully. A new `docs/community-interpreters/` directory
holds contributed reference YAMLs (PRs welcome — they are examples,
not curated products). Full format reference is in `docs/PRD.md`
§11. **Requires STD v0.6.0 or later**; earlier STD versions reject
the unknown `exposure_observations` key.
- Debug-only env var `INTERPRETER_RELOAD_ON_EACH_EVENT`. When truthy,
re-reads the YAML interpreter directories on every dispatch instead
of once at startup. For iterating on a YAML; not for production
use.
- New env var `STD_REPORT_ALL_CONTAINERS`. When set to a truthy value
(`true`, `1`, `yes` — case-insensitive), the notifier reports every
running container on the host to STD, regardless of whether the
container has the `dockernotifier.notifiers=service-tracker-dashboard`
opt-in label. Default off — existing per-container opt-in behavior
is unchanged for operators who don't set the variable. **Only STD
dispatch is affected**: the DNS notifier still requires explicit
per-container opt-in via labels, because DNS records are an external
side effect that shouldn't fire for containers that didn't ask.
Unrecognized values (e.g. `maybe`) log a warning at startup and are
treated as off. The wire payload to STD is identical whether the
trigger came from a label or from this env var.
- STD payloads now include `networks`, `exposed_ports`, and
`published_ports` read directly from the Docker API. `networks` is a
list of `{"name", "aliases"}` objects, one per Docker network the
container is on. `exposed_ports` is a list of `"<port>/<proto>"`
strings from the container's `ExposedPorts` config. `published_ports`
is a list of `{"container_port", "protocol", "host_ip", "host_port"}`
objects, one per port binding. Empty values are emitted as explicit
empty lists so STD can distinguish "nothing to report" from "not yet
reported". No new env vars or labels — capture is automatic for every
container reported to STD. **Requires STD v0.6.0 or later**; STD
v0.5.x's strict validator will reject payloads carrying these keys.

### Changed
- **Design principles softened (v0.4.0).** Previously, PRD §1.3 stated
"no state" and "all configuration is via environment variables.
There is no config file." Both are now narrower. The notifier still
holds no per-event state and still has no central config file, but
it now loads YAML interpreter files at startup. Operators upgrading
from v0.3.x do not need to take any action — the YAML loader works
out of the box with the two baked-in interpreters, and the
filesystem footprint is fully contained inside the notifier
container.

### Fixed
- Docker event loop no longer logs `Failed to handle <action> event for
None: Resource ID was not provided` errors. The container ID is now
read from `event["Actor"]["ID"]` (newer Docker daemons no longer
populate the legacy top-level `id` field), non-container events
(network/volume/service) are filtered out before lookup, and the
expected `NotFound` raised when querying an already-removed container
on a `destroy` event is swallowed silently.

---

## [0.3.0] — 2026-05-12
Expand Down Expand Up @@ -101,7 +168,8 @@ Released.

Initial public release.

[Unreleased]: https://github.com/crzykidd/docker-api-notifier/compare/v0.3.0...HEAD
[Unreleased]: https://github.com/crzykidd/docker-api-notifier/compare/v0.4.0...HEAD
[0.4.0]: https://github.com/crzykidd/docker-api-notifier/releases/tag/v0.4.0
[0.3.0]: https://github.com/crzykidd/docker-api-notifier/releases/tag/v0.3.0
[0.2.3]: https://github.com/crzykidd/docker-api-notifier/releases/tag/v0.2.3
[0.2.2]: https://github.com/crzykidd/docker-api-notifier/releases/tag/v0.2.2
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ FROM python:3.11-slim
WORKDIR /app

COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install --no-cache-dir -r requirements.txt \
&& mkdir -p /app/interpreters/user
ENV PYTHONUNBUFFERED=1
CMD ["python", "main.py"]
103 changes: 95 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ every Docker host without it touching things you didn't ask it to touch.
1. [What It Does](#what-it-does)
2. [Environment Variables](#environment-variables)
3. [Container Labels](#container-labels)
4. [Docker Compose Example](#docker-compose-example)
5. [How It Works](#how-it-works)
6. [Building Locally](#building-locally)
4. [Interpreters](#interpreters)
5. [Docker Compose Example](#docker-compose-example)
6. [How It Works](#how-it-works)
7. [Building Locally](#building-locally)

---

Expand Down Expand Up @@ -80,11 +81,19 @@ notifier targets can be added without touching the core event loop.
> notifier posts to STD's `/api/v1/register` endpoint using STD's
> canonical schema. Earlier STD versions do not expose that endpoint
> and will return 404.

| Variable | Required | 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. |
>
> **Notifier v0.4.0 requires STD v0.6.0 or later.** Starting in v0.4.0
> the notifier emits `networks`, `exposed_ports`, `published_ports`,
> and `exposure_observations` on 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 silently
no-ops — the container won't fail to start. This is intentional so you
Expand Down Expand Up @@ -138,6 +147,79 @@ applies its own defaults for anything you don't.
> coercion happens at the same boundary, so the values STD receives
> are actual `bool`/`int` rather than strings.

> **Network and port data.** As of notifier v0.4.0, every STD payload
> also carries `networks`, `exposed_ports`, and `published_ports`
> read 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+.

---

## Interpreters

> **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.

### What ships built in

Two interpreters live inside the container image at
`/app/interpreters/builtin/`:

- `traefik.yml` — reads `traefik.http.routers.<router>.rule`
(hostname), `.tls`, and `.entrypoints`. Emits one observation per
router on the container.
- `dockflare.yml` — fires when `dockflare.enable=true`; reads
`dockflare.hostname` and optional Access policy labels. Emits a
single observation with `tls: 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.

### Adding your own

Mount a directory of YAML files into the container at
`/app/interpreters/user/`:

```yaml
volumes:
- ./my-interpreters:/app/interpreters/user:ro
```

User 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.

### Format

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](docs/PRD.md) for the full reference,
or `docs/community-interpreters/template.yml` for an annotated
skeleton.

### Community reference

`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.

### Empty list vs. absent on the wire

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.

---

## Docker Compose Example
Expand All @@ -159,6 +241,8 @@ services:
- /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-stopped
```

Expand All @@ -170,6 +254,9 @@ Volumes:
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](#interpreters) above.

---

Expand Down
Loading
Loading