fix(fleet-prometheus): bind listen socket to Tailscale IP, not 0.0.0.0

Previously published "9090:9090" → Docker bound to 0.0.0.0 (all host interfaces,
including the public Hetzner IP), leaving tailscale-internal enforced only by the
VPS firewall. Now bind explicitly to the VPS Tailscale interface for
defense-in-depth: the port does not exist on the public IP at all.

- ports -> "${TAILSCALE_BIND_IP}:9090:9090"
- env.example: add TAILSCALE_BIND_IP (VPS Tailscale IP, verify via `tailscale ip -4`)
- README: deploy section — .env is mandatory; a missing .env makes Compose
  silently bind 0.0.0.0 (warns, does not fail), so use --env-file and verify host_ip

Smoke: config with --env-file and with co-located .env both resolve
host_ip=100.95.58.48; with .env absent Compose warns and falls back to 0.0.0.0
(documented). .env is gitignored (global *.env rule).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
oskar 2026-06-24 17:38:48 +02:00
parent 01a0314176
commit 43c47a0a55
3 changed files with 45 additions and 13 deletions

View file

@ -47,7 +47,34 @@ TSDB lives in the named volume `fleet-prometheus_tsdb` → `/prometheus`.
## Configuration
- `prometheus.yml` — committed, **secret-free**. Global config + two scrape jobs.
- `env.example` — no secrets / no required env (kept only for layout parity).
- `env.example` → copy to `.env` (gitignored). Holds `TAILSCALE_BIND_IP`, the
VPS Tailscale interface IP the listen socket is bound to.
### Deploy — `.env` is mandatory
The port is published as `${TAILSCALE_BIND_IP}:9090:9090` so the socket is bound
**only** to the Tailscale interface, never `0.0.0.0`. Compose auto-loads a
co-located `services/fleet-prometheus/.env`, but if that file is **missing** the
variable resolves to a blank string and Compose **silently binds to 0.0.0.0**
(publicly reachable on the Hetzner IP) — it only warns, it does not fail.
Therefore, before `up -d`:
1. Create `services/fleet-prometheus/.env` from `env.example` with the real
`TAILSCALE_BIND_IP` (verify with `tailscale ip -4` on the VPS).
2. Deploy with the env file explicit, to be safe:
```bash
docker compose -f services/fleet-prometheus/docker-compose.yml \
--env-file services/fleet-prometheus/.env up -d
```
3. Confirm the bind is Tailscale-only (must NOT show `0.0.0.0`):
```bash
docker compose -f services/fleet-prometheus/docker-compose.yml \
--env-file services/fleet-prometheus/.env config | grep host_ip
```
### Scrape targets (scaffold)

View file

@ -20,11 +20,14 @@ services:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- fleet_prometheus_tsdb:/prometheus
ports:
# tailscale-internal: exposed exactly like control-plane (plain published
# HOST:CONTAINER mapping, no public npm reverse-proxy entry). Reachability
# is constrained to the Tailscale mesh at the VPS firewall layer, same as
# control-plane's 18180. Do NOT add this to npm / public DNS.
- "9090:9090"
# tailscale-internal: the listen socket is bound ONLY to the VPS Tailscale
# interface IP (TAILSCALE_BIND_IP), never 0.0.0.0. The port therefore does
# not exist on the public Hetzner IP at all — reachability is enforced in
# the bind itself (defense-in-depth), not solely by the host firewall.
# brain-watchdog on PIHA reaches this over the VPS Tailscale IP.
# Requires --env-file services/fleet-prometheus/.env at deploy (see README).
# Do NOT add this to npm / public DNS.
- "${TAILSCALE_BIND_IP}:9090:9090"
# node_exporter on the VPS runs with network_mode: host, so it listens on
# the host's :9100. This bridged container reaches it via host-gateway.
extra_hosts:

View file

@ -1,8 +1,10 @@
# fleet-prometheus has NO secrets and NO required environment.
# fleet-prometheus has NO secrets, but it does require one host-local value for
# the Tailscale-only port bind. Copy this file to .env (gitignored) and pass it
# at deploy with --env-file services/fleet-prometheus/.env.
#
# Configuration lives entirely in prometheus.yml (secret-free by contract) and
# the compose command flags. There is intentionally nothing to copy to a .env.
#
# This template exists only to keep the standard service file layout. If a
# future step needs env (e.g. an Alertmanager URL), add it here and wire an
# env_file into docker-compose.yml at that point.
# Configuration otherwise lives entirely in prometheus.yml (secret-free by
# contract) and the compose command flags.
# Tailscale IP węzła VPS (ubuntu-4gb-hel1-1). Bind nasłuchu Prometheus
# TYLKO do mesha — nie 0.0.0.0. Zweryfikuj przy odtwarzaniu hosta: tailscale ip -4.
TAILSCALE_BIND_IP=100.95.58.48