Files
watchtower-observability-stack/docs/architecture.md
T
2026-05-09 20:08:13 -05:00

3.5 KiB

Architecture

Overview

Watchtower is a single-node observability stack running on a Hetzner CPX42 in Nuremberg (nbg1). It hosts Mimir (metrics), Loki (logs), Tempo (traces), and Grafana (UI). Clients ship telemetry over a WireGuard VPN; nothing is exposed to the public internet.

Diagram

                  Internet
                     │
         ┌───────────┴───────────┐
         │ Hetzner Cloud Firewall│  allow: 22/tcp, 51820/udp
         └───────────┬───────────┘
                     │
              ┌──────┴──────┐
              │ AlmaLinux 10│
              │   CPX42     │
              │             │
              │  nftables   │  default-drop input + fail2ban
              │  ┌──────────┴──────────┐
              │  │ wg0 (10.8.0.1/24)   │  ◄── WireGuard peers
              │  └──┬──────────────────┘
              │     │ all service ports bind to 10.8.0.1
              │  ┌──┴──────────────────────┐
              │  │ Podman observability    │
              │  │ network (172.20.0.0/24) │
              │  │  mimir   :8080 :9095    │
              │  │  loki    :3100          │
              │  │  tempo   :3200/4317/4318│
              │  │  grafana :3000          │
              │  └──┬──────────────────────┘
              │     │
              │  /var/lib/observability  ◄── LUKS2 (xfs)
              └─────┘
                     │ S3 API (HTTPS)
                     ▼
             Hetzner Object Storage (nbg1)
              - watchtower-mimir
              - watchtower-loki
              - watchtower-tempo

Data flow

  1. Clients run Grafana Alloy with PII scrubbing pipelines (see config/alloy/config.alloy.example).
  2. Alloy ships logs → Loki, traces → Tempo OTLP, metrics → Mimir remote_write, all over WireGuard with X-Scope-OrgID headers.
  3. Mimir/Loki/Tempo write blocks/chunks to Hetzner Object Storage. WALs and compactor scratch live on the LUKS-encrypted local volume.
  4. Grafana queries Mimir/Loki/Tempo via the observability Podman bridge (container DNS), with tenant header injected per Org's datasource config.

Components and ports

Service Container Bind Public?
Mimir mimir 10.8.0.1:8080,9095 no
Loki loki 10.8.0.1:3100 no
Tempo tempo 10.8.0.1:3200, OTLP 4317/4318 no
Grafana grafana 10.8.0.1:3000 no

Storage

Layer Backend Encryption
Long-term Hetzner S3 Server-side (Hetzner SSE)
Local WAL LUKS2 + xfs At-rest (LUKS2 on /dev/sdX or loop)
Transit WireGuard ChaCha20-Poly1305

Lifecycle

  • OpenTofu provisions the server, firewall, and S3 buckets (state in Cloudflare R2).
  • Ansible configures AlmaLinux, LUKS, nftables + fail2ban, WireGuard, and deploys the Podman Quadlets.
  • systemd Quadlet generator turns *.container files into mimir.service, loki.service, tempo.service, grafana.service.