Skip to content

Commit 63c0158

Browse files
ulises-cclaude
andcommitted
feat(server): expose watchtower metrics API + sidecar for Uptime Kuma monitoring
Watchtower is headless (no UI), so to track up/down like the other services it gets a sidecar fronting only its token-gated HTTP metrics API: WATCHTOWER_HTTP_API_METRICS=true + WATCHTOWER_HTTP_API_TOKEN, served at https://watchtower.<tailnet>.ts.net/v1/metrics. Metrics-only — NOT the update API — so periodic polls (the 3am WATCHTOWER_SCHEDULE) keep running. Uptime Kuma monitors it with an HTTP check + 'Authorization: Bearer <token>' header (recipe in HTTPS.md). No homepage href — the endpoint is an API, not a UI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bb22c1b commit 63c0158

5 files changed

Lines changed: 77 additions & 0 deletions

File tree

TODO.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ and full rollout table in [linux-server/HTTPS.md](linux-server/HTTPS.md).
198198
`host.docker.internal:8088`. Verified 200 with no redirect, real page
199199
content, confirmed in browser. Don't use port `:5252` — see HTTPS.md
200200
Gotchas
201+
- [ ] watchtower — no UI; sidecar scaffolded to front only its token-gated
202+
`/v1/metrics` API (`WATCHTOWER_HTTP_API_METRICS=true`). Pending live apply +
203+
an Uptime Kuma HTTP monitor on `https://watchtower.<tailnet>.ts.net/v1/metrics`
204+
with a Bearer-token header (recipe in HTTPS.md). Metrics-only, so the 3am
205+
schedule is unaffected.
201206
- [x] Decide auth method: OAuth client + `tag:container` (reusing the elevated
202207
tailscale-proxy client) — resolved during the Forgejo rollout, see HTTPS.md
203208
- [x] Decide whether to retire NPM or keep it — KEEP, as the non-tailnet HTTPS

linux-server/HTTPS.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ side of the `ports:` mapping (`host:container`), not the host side.
230230
| homepage | 3000 | ✅ done | host-networked variant — keep `network_mode: host` (reaches localhost widgets), sidecar proxies via `host.docker.internal`; add the domain to `HOMEPAGE_ALLOWED_HOSTS` |
231231
| cockpit | 9090 | ✅ done | host systemd service — **sidecar-only** stack proxies `https+insecure://host.docker.internal:9090`; `cockpit.conf.example`'s `Origins` line turned out to be unnecessary in practice — see Gotchas |
232232
| tailscale-web | 8088 | ✅ done | not in the original rollout — added because the homepage Tailscale tile linked plain HTTP. `tailscale web` is a host **systemd user unit**, not a container; `ExecStart` needs `--listen 0.0.0.0:8088 --origin https://tailscale-web.<tailnet>.ts.net` so it's reachable via `host.docker.internal` and knows it's reverse-proxied. Don't use port `:5252` — see Gotchas |
233+
| watchtower | 8080 | todo | **no UI** — the sidecar fronts only watchtower's token-gated `/v1/metrics` HTTP API (enable `WATCHTOWER_HTTP_API_METRICS=true` + `WATCHTOWER_HTTP_API_TOKEN`); no homepage `href`. Monitor it in Uptime Kuma — see below |
233234

234235
Services that also expose **non-HTTP** ports the LAN/tailnet needs (AdGuard DNS
235236
`:53`, Syncthing sync `:22000`, Forgejo SSH `:22`) keep those as direct
@@ -244,6 +245,25 @@ non-empty. `ts-state/` is already gitignored for every service
244245
(`linux-server/*/ts-state/`). Remember `docker compose up -d` (not `restart`)
245246
for `homepage` afterward — see Homepage links below.
246247

248+
### Monitoring a UI-less service in Uptime Kuma (watchtower)
249+
250+
Watchtower is a headless daemon — no UI, nothing to click. To get the same
251+
up/down tracking as the other services, enable its HTTP metrics API and point an
252+
Uptime Kuma HTTP monitor at it (metrics-only, so the `WATCHTOWER_SCHEDULE` keeps
253+
running — only the *update* API would disable periodic polls):
254+
255+
1. In `watchtower/.env`: set `WATCHTOWER_API_TOKEN` (e.g. `openssl rand -hex 32`)
256+
and `TS_AUTHKEY`; `docker compose up -d`.
257+
2. In Uptime Kuma, add an **HTTP(s)** monitor:
258+
- URL: `https://watchtower.<tailnet>.ts.net/v1/metrics`
259+
- Header: `Authorization: Bearer <WATCHTOWER_API_TOKEN>`
260+
- Accepted status codes: `200` (an unauthenticated probe gets `401`, so the
261+
header is what proves it's both up *and* reachable).
262+
263+
The same pattern fits any future no-UI service that exposes a health/metrics
264+
endpoint. For a daemon with *no* endpoint at all, Uptime Kuma's "Docker Container"
265+
monitor (via the docker socket) checks the container's running state instead.
266+
247267
## Gotchas / migration
248268

249269
- **Existing git remotes** pointing at `http://...:3300` must be updated:
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copy this file to .env and set the values. See ../HTTPS.md for the full setup.
2+
3+
# Bearer token guarding watchtower's HTTP API (/v1/metrics). Generate any random
4+
# string, e.g.: openssl rand -hex 32
5+
# Uptime Kuma sends it as the header: Authorization: Bearer <this value>
6+
WATCHTOWER_API_TOKEN=
7+
8+
# Tailscale auth for the sidecar — same OAuth client secret as the other sidecars
9+
# (needs the Auth Keys scope + tag:container in the ACL).
10+
TS_AUTHKEY=
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,45 @@
11
services:
2+
# Tailscale sidecar — joins the tailnet as `watchtower`, terminating HTTPS for
3+
# https://watchtower.<tailnet>.ts.net. Watchtower has no human UI; this fronts
4+
# only its token-gated /v1/metrics API so Uptime Kuma can health-check it.
5+
# See ../HTTPS.md.
6+
watchtower-ts:
7+
image: tailscale/tailscale:latest
8+
container_name: watchtower-ts
9+
hostname: watchtower
10+
environment:
11+
- TS_AUTHKEY=${TS_AUTHKEY}
12+
- TS_STATE_DIR=/var/lib/tailscale
13+
- TS_SERVE_CONFIG=/config/serve.json
14+
# Requires TS_AUTHKEY to be allowed to apply tag:container (OAuth client, or
15+
# a tag-scoped auth key). Drop this line if using an untagged personal key.
16+
- TS_EXTRA_ARGS=--advertise-tags=tag:container
17+
volumes:
18+
- ./ts-state:/var/lib/tailscale
19+
- ./ts-serve.json:/config/serve.json:ro
20+
devices:
21+
- /dev/net/tun:/dev/net/tun
22+
cap_add:
23+
- NET_ADMIN
24+
restart: unless-stopped
25+
226
watchtower:
327
image: containrrr/watchtower:latest
428
container_name: watchtower
29+
# Share the sidecar's netns: tailscale serve proxies :443 to the HTTP API on
30+
# 127.0.0.1:8080. The docker socket is a file mount, unaffected by the netns.
31+
network_mode: service:watchtower-ts
32+
depends_on:
33+
- watchtower-ts
534
restart: unless-stopped
635
environment:
736
- WATCHTOWER_SCHEDULE=0 0 3 * * * # 3am daily (6-field cron: sec min hr dom mon dow)
837
- WATCHTOWER_CLEANUP=true # remove old images after updating
938
- DOCKER_API_VERSION=1.40 # override default 1.25 — incompatible with Docker 29.x
39+
# HTTP API: metrics only (NOT the update endpoint), so the schedule above
40+
# keeps running — only the update API disables periodic polls. Serves
41+
# GET /v1/metrics on :8080, gated by the Bearer token below.
42+
- WATCHTOWER_HTTP_API_METRICS=true
43+
- WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_API_TOKEN}
1044
volumes:
1145
- /var/run/docker.sock:/var/run/docker.sock
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"TCP": { "443": { "HTTPS": true } },
3+
"Web": {
4+
"${TS_CERT_DOMAIN}:443": {
5+
"Handlers": { "/": { "Proxy": "http://127.0.0.1:8080" } }
6+
}
7+
}
8+
}

0 commit comments

Comments
 (0)