Skip to main content
The prober (cmd/prober) is the outside view of the platform: every 30 s it sends a real web event and a real OTLP metric for the internal probe tenant, reads them back through the machine API, checks the console, the Umami host, the tracker files against the release manifest, and every node’s certificate by address. It must not run inside the system it watches, so it runs on a host outside Azure. This document runs it on the owner’s Home LXC, beside the Uptime Kuma and ntfy that already alert the owner’s phone, and makes Kuma alert on its results. Nothing here changes the Home LXC by itself. The steps below are for the owner to run there.

1. What runs

infra/ansible/playbooks/probe-bundle.yml renders a self-contained Docker Compose bundle on the controller, from the same templates the observability role uses: Gatus is published only on the address in PROBE_BIND; nothing listens publicly. Kuma reads Gatus’s per-check status API, one Kuma monitor per check, and Kuma’s existing ntfy notification delivers the alert.

2. Render the bundle (controller)

Needs the environment’s inventory (make <env>-apply), its SOPS file with the probe tenant (platformctl probe-tenant create, bootstrap.md step 10) and the release manifest:
Expected: the release verification, then probe bundle for production in .../resources/probe-bundle/production. The directory is mode 0700 under resources/, which Git ignores.

3. Run it on the Home LXC (owner)

Copy the directory and start it. The Home LXC’s containers reach host services at 172.17.0.1 (the owner’s server notes, Docker networking), so Gatus is published there on port 8095. Check first that nothing else uses the port: ssh home 'ss -ltn "( sport = :8095 )"' must list no socket; otherwise change probe_bundle_port (-e probe_bundle_port=<port>).
Expected: both containers Up, and a JSON list of endpoints within a minute. The bundle is its own Compose project (veristar-probe-production) beside /home/docker/docker-compose.yml, so the Home stack is not touched. The Home LXC resolves names through its own AdGuard; the platform’s hostnames are under .app. On 2026-11-13 AdGuard answered a made-up .app name with no address (not 0.0.0.0), so the TLD is not blocked as a whole. Check the real names once they exist: dig +short in.veristar.app @192.168.1.102 must not answer 0.0.0.0. Backups: the bundle holds only configuration and Gatus’s history; the Home restic job already covers /home/docker unless an exclude matches.

4. Add the Uptime Kuma monitors (owner, Kuma web UI)

One monitor per check. For each, in Uptime Kuma: Add New Monitor. The query is true only when the newest result is younger than three minutes and passed, so a stopped prober alerts too, not only a failed check. Gatus page 1 with pageSize=1 holds exactly the newest result (checked with Gatus 5.38.0 on 2026-11-13). The keys (Gatus key of group and name): The full list is at http://172.17.0.1:8095/api/v1/endpoints/statuses (field key). Kuma’s own status page is behind Cloudflare Access, so these monitors are for the owner, not a public status page.

5. Public status page and the alert heartbeat

The bundle has no public listener. status.<BASE_DOMAIN> (the status page) and the Alertmanager watchdog push are served only when a probe host is in the inventory’s probe group, with its own public address and Caddy (site.yml, play Probe). With the probe on the Home LXC, the status page can be published by a Cloudflare Tunnel route to Gatus, for example through the Home LXC’s existing cloudflared, as a separate decision of the owner: it is a change to the Home LXC and to the Cloudflare account. The nightly slo-report.yml workflow reads Gatus’s results from STATUS_BASE_URL (https://status.<input I4>), so the monthly SLO report needs that publication too. Until then the Gatus history on the Home LXC holds the measurements (about a week of raw results), and Kuma keeps its own uptime history per monitor.

6. What was checked

  • ansible-playbook --syntax-check and make ansible-lint pass for the playbook and template (2026-11-13).
  • The Gatus API answers used by the Kuma query, with Gatus 5.38.0 and three pushed results: pageSize=1 returned only the newest (success: false after true, true, false).
  • Not run yet: the bundle against a live environment (there is none before the first apply).