Files
HiveGrid-Wireframes/hivegrid-cloud/docs/improvement-plan/02-observability/sentry-selfhost-setup.md
T

18 KiB

Sentry Self-Host Setup

Status: in_progress (deployed-on-staging, hardening+backup-verify-pending) Task-ID: #29 Phase: 02 Estimated Effort: 3-4 Tage Owner: unassigned

Problem

Aktuell existiert in packages/error-reporter/ zwar ein Sentry-Adapter (sentry.ts), aber es ist kein Sentry-Backend deployed. Es gibt keinen DSN, keine Project-Keys, keine zentrale Stelle wo Server-Errors aggregiert werden. Damit:

  • Production-Fehler verschwinden in den Container-Logs (Coolify-Log-Rotation)
  • Keine Release-Korrelation, keine Breadcrumbs, keine Performance-Daten
  • Kein Replay für Frontend-Crashes
  • Phase 03 (Skalierungs-Refactoring) wäre blind — Regressionen würden erst entdeckt wenn ein User reklamiert

Wir wollen kein SaaS-Sentry wegen DSGVO-Profil + bereits vorhandenem Hetzner-Server mit Coolify. Stattdessen self-hosted Sentry via offizielles getsentry/self-hosted Repo.

Betroffene Codestellen (nach diesem Task wird neu angelegt):

  • infra/sentry/docker-compose.yml (neu)
  • infra/sentry/install.sh (Wrapper, neu)
  • infra/sentry/sentry/config.yml (neu)
  • infra/sentry/.env.example (neu)
  • docs/observability/sentry-selfhost.md (neu)
  • docs/observability/sentry-glitchtip-alternative.md (neu)

Depends on

  • 01-tech-debt-foundation/dep-updates.md — wir wollen Sentry-SDK-Versionen erst nach den Dep-Updates fixieren, sonst gibt es Major-Conflicts (Sentry v8 ↔ alte http-Stacks)

Blocks

  • 02-observability/sentry-wiring-api-worker.md — braucht funktionierende DSN
  • 02-observability/sentry-wiring-web.md — braucht funktionierende DSN + Project
  • 02-observability/sentry-ci-integration.md — braucht funktionierendes Org/Project für sentry-cli
  • 02-observability/opentelemetry.md — Relay-OTLP-Endpoint muss in Sentry-Compose freigeschaltet sein
  • 03-scaling-infrastructure/* — alle Refactorings wollen Sentry-Sichtbarkeit
  • 09-authentik-admin-sso/authentik-setup.md — Authentik selbst soll an Sentry reporten

Approach

Gewählt: Offizielles getsentry/self-hosted Repo, gepinnt auf v25.x (LTS-artige Reihe), deployed als Coolify-managed Docker-Compose-Stack auf dem Hetzner-Server. Eigene Subdomain sentry.<domain> via Caddy/Traefik mit TLS aus Coolify-Built-in-Cert-Management.

Begründung:

  • Offizielle Compose statt eigene Bastelei → wir bekommen Upgrades, Security-Patches und Symbolicator-/Snuba-/Relay-Tuning für umsonst.
  • Coolify als Orchestrator statt manuelles docker compose up, weil der Hetzner-Server eh damit läuft → Backups, Restarts, TLS sind dann durch Coolify's UI verwaltbar.
  • Alternative GlitchTip (leichter, ~3 Container statt 10) wird als optionaler "Light-Variante" dokumentiert (sentry-glitchtip-alternative.md), aber nicht als Default gewählt — wir wollen Performance/Tracing + Replay + OTLP-Ingest, und GlitchTip kann das nicht vollständig.
  • v25.x statt latest, weil self-hosted Sentry historisch Breaking-Changes in Minor-Releases hatte; wir wollen kontrolliert upgraden.

Ressourcenprofil:

  • ~10 Container: web, worker, cron, relay, snuba-api, snuba-consumer, kafka, clickhouse, postgres, redis, symbolicator, smtp, nginx
  • 16 GB RAM empfohlen (12 GB hartes Minimum, Clickhouse + Kafka sind RAM-hungrig)
  • 4 vCPU empfohlen
  • ~50 GB Disk für 90 Tage Event-Retention (kleines Team)

Implementation Steps

  1. Repo-Skelett anlegen — neues Verzeichnis infra/sentry/ mit README.

    • Files: infra/sentry/README.md, infra/sentry/.gitignore (postgres-data, clickhouse-data, kafka-data ausschließen)
    • Commit: 02-observability/sentry-selfhost: scaffold infra/sentry directory
  2. Pinning & Sub-Repo — getsentry/self-hosted v25.x als git-submodule ODER vendored snapshot (Entscheidung: vendored snapshot, weil submodule + Coolify schlecht zusammenspielen).

    • Files: infra/sentry/upstream/ (snapshot der relevanten Compose + sentry/config.yml + install.sh)
    • Commit: 02-observability/sentry-selfhost: vendor getsentry/self-hosted v25.x snapshot
  3. Compose-Overlay — docker-compose.override.yml mit projekt-spezifischen Anpassungen:

    • Network sentry-net
    • Volumes mit absoluten Coolify-Pfaden
    • SENTRY_USE_SSL=1 (TLS-Termination durch Caddy/Traefik vor dem Container)
    • SENTRY_SINGLE_ORGANIZATION=true, SENTRY_FEATURES='organizations:relay' für OTLP
    • Public-Signup deaktiviert: SENTRY_FEATURES['auth:register']=False in sentry/sentry.conf.py
    • Files: infra/sentry/docker-compose.override.yml, infra/sentry/sentry/sentry.conf.py
    • Commit: 02-observability/sentry-selfhost: add compose overlay with hardened config
  4. install.sh-Wrapper — eigener Wrapper infra/sentry/install.sh, der das upstream-install.sh mit unseren Defaults aufruft und am Ende einen Superuser anlegt (SENTRY_ADMIN_EMAIL / SENTRY_ADMIN_PASSWORD aus Env).

    • Files: infra/sentry/install.sh, infra/sentry/.env.example
    • Commit: 02-observability/sentry-selfhost: add install wrapper script
  5. Reverse-Proxy + TLS — Caddy- oder Traefik-Snippet (entscheide nach Coolify-Default — auf bestehendem Hetzner-Server ist Traefik der Default in Coolify v4) für sentry.<staging-domain> mit Auto-TLS via Let's Encrypt.

    • Files: infra/sentry/traefik.labels.yml (Compose-Labels-Snippet zum Mergen)
    • Commit: 02-observability/sentry-selfhost: add traefik labels for sentry subdomain
  6. OTLP-Ingest aktivieren — relay/config.yml so anpassen, dass Relay einen OTLP-HTTP-Endpoint (/api/{project}/integration/otlp/v1/traces) annimmt. Wird in opentelemetry.md als Sender konsumiert.

    • Files: infra/sentry/relay/config.yml
    • Commit: 02-observability/sentry-selfhost: enable OTLP ingest in relay
  7. Backup-Strategie — täglicher Cron-Container, der:

    • postgres-dump mit pg_dump --format=custom → S3-Upload via rclone oder aws s3 cp
    • Clickhouse-Snapshot via clickhouse-backup → S3-Upload
    • Sentry-Encryption-Key (SENTRY_SECRET_KEY in .env) extra notieren — ohne den Key sind Backups wertlos
    • Files: infra/sentry/backup/Dockerfile, infra/sentry/backup/backup.sh, infra/sentry/backup/crontab
    • Commit: 02-observability/sentry-selfhost: add daily backup container
  8. Update-Strategie dokumentieren — docs/observability/sentry-selfhost.md mit:

    • Wie man von v25.x.a auf v25.x.b geht (./install.sh re-run)
    • Wie man auf Major v26 upgraded (vorher Backup, Release-Notes lesen, Snuba-Migrations können brechen)
    • Wie man die Datenbank ent-bloated (ältere Events purgen via sentry cleanup --days 90)
    • Files: docs/observability/sentry-selfhost.md
    • Commit: 02-observability/sentry-selfhost: document update + maintenance strategy
  9. Hardening-Checkliste — Anhang im Doku-File:

    • Public-Signup aus
    • Standard-Admin-Passwort wechseln (über Wrapper-Script erzwingen)
    • 2FA für Admin-User aktivieren (Sentry hat eingebautes TOTP)
    • Rate-Limit auf Relay konfigurieren (kein Open Ingest)
    • DSN-Whitelist via Project-Settings → "Allowed Domains"
    • Files: docs/observability/sentry-selfhost.md (Erweiterung)
    • Commit: 02-observability/sentry-selfhost: add hardening checklist
  10. GlitchTip-Alternative dokumentieren — separates Doku-File für den Fall, dass der RAM-Bedarf von Sentry-Self-Host kritisch wird (z.B. wenn auf 8-GB-Server verschoben).

    • Files: docs/observability/sentry-glitchtip-alternative.md mit kompletter Light-Compose (postgres + redis + glitchtip-web + glitchtip-worker), DSN-kompatibel zum Sentry-SDK, ohne Performance/Tracing/Replay-Features.
    • Commit: 02-observability/sentry-selfhost: document glitchtip light alternative
  11. Coolify-Deployment + Smoke-Test — Stack auf Staging deployen, manuell Test-Event von lokalem sentry-cli send-event triggern, in der UI prüfen.

    • Files: keine (Operations-Schritt, wird in ## Progress Log festgehalten)
    • Commit: keiner (Deployment, kein Code)

Test Requirements

  • Unit-Tests: keine direkten — das ist Infra. Aber:
  • Smoke-Tests (manuell, Staging):
    • Sentry-Web-UI erreichbar unter https://sentry.<staging-domain>
    • Admin-Login funktioniert mit .env-Credentials
    • Test-Event via sentry-cli send-event -m "smoke-test-$(date)" wird in Issues-View sichtbar
    • OTLP-Endpoint antwortet auf POST /api/{project_id}/integration/otlp/v1/traces mit 202
    • Public-Signup-Page https://sentry.<staging-domain>/auth/register/ ist 404
    • Backup-Container schreibt nach 24h einen Postgres-Dump nach S3 (prüfen via aws s3 ls)
  • Integration-Test (Skript): infra/sentry/scripts/smoke.sh der die obigen Punkte automatisiert prüft und in CI laufen kann
  • Coverage-Target: N/A für Infra; Smoke-Skript MUSS exit-code-sauber sein
  • Test-Files: infra/sentry/scripts/smoke.sh + infra/sentry/scripts/smoke.test.sh (bats oder bash-mit-set-e)

NICHT als completed markieren bevor alle Smoke-Tests auf Staging grün sind.

Acceptance Criteria

  • infra/sentry/ vollständig mit Compose + Overlay + install-Wrapper + Backup-Container
  • Sentry auf Staging-Hetzner deployed (Coolify-managed), erreichbar über TLS-Subdomain
  • DSN für 3 Projekte erzeugt: boilerplate-api, boilerplate-worker, boilerplate-web
  • Public-Signup deaktiviert, Admin-2FA aktiviert
  • OTLP-Endpoint funktionsfähig (curl-Smoke grün)
  • Tägliches Backup nach S3 läuft (Verifikation nach 24h)
  • docs/observability/sentry-selfhost.md enthält: Deploy-Schritte, Update-Strategie, Backup/Restore-Drill, Hardening-Checkliste, Ressourcen-Anforderungen
  • docs/observability/sentry-glitchtip-alternative.md mit Light-Compose und DSN-Kompatibilitäts-Notiz
  • .env.example enthält SENTRY_ADMIN_EMAIL, SENTRY_ADMIN_PASSWORD, SENTRY_SECRET_KEY, SENTRY_DSN_API, SENTRY_DSN_WORKER, SENTRY_DSN_WEB, SENTRY_S3_BACKUP_*
  • Smoke-Skript grün
  • pnpm lint + pnpm typecheck grün (keine TS-Code-Änderungen — bleibt grün)
  • Detail-MD Progress Log aktualisiert
  • PROGRESS.md Status auf completed

Rollback Strategy

  • Compose-Stack auf Coolify stoppen — Sentry-Stack down, Apps reporten dann nichts mehr aber laufen weiter (kein App-Impact, weil DSN-Calls fire-and-forget mit Timeout sind)
  • Daten-Backup behalten — Postgres + Clickhouse-Volumes mindestens 30 Tage aufbewahren bevor gelöscht
  • DSN-Rotation falls geleakt: über Sentry-UI neue Keys ausstellen, alte invalidieren
  • Compose-Revert via git revert der entsprechenden Commits — Coolify re-deployed dann den vorherigen Stand. Hetzner-Server-Disk-Quota beachten (alte Volumes manuell docker volume rm falls Platzmangel)

Security Notes

  • SENTRY_SECRET_KEY ist sensitiv — wenn der Key geleaked wird, sind alle gespeicherten Session-Daten kompromittiert. In Coolify-Secrets ablegen, nie in Git.
  • Postgres-/Clickhouse-Ports nicht public — nur via Docker-internes Netz erreichbar, NICHT als published port in docker-compose.yml.
  • Relay-Rate-Limit auf z.B. 100 events/sec pro Project setzen, sonst Bot-Spam-Risiko (DSN ist öffentlich im Frontend-Code sichtbar).
  • Allowed-Domains-Whitelist im Sentry-Project setzen, damit niemand fremde Origins Events einsenden kann.
  • Backups verschlüsseln — pg_dump mit gpg-Pipe oder S3-Side-Encryption mit eigener KMS-Key.
  • Admin-Account 2FA ist Pflicht — Sentry hat eingebautes TOTP, im Wrapper-Script aktivieren-Hinweis ausgeben.
  • Symbolicator soll keine externen Symbol-Server kontaktieren (Privatsphäre der Stack-Traces) — SYMBOLICATOR_SYMSOURCES=[] setzen.

Subagent Recommendations

  • Planning: Plan-Subagent mit dieser Detail-MD als Input — soll konkreten Deployment-Plan für Hetzner+Coolify ausarbeiten (welche Coolify-API-Endpoints, welche Reihenfolge)
  • Research: Explore-Subagent für: "aktuelle stabile getsentry/self-hosted Tag-Version v25.x, prüfe Release-Notes auf Breaking-Changes seit letzter LTS"
  • Implementation: general-purpose-Subagent mit Brief: "Lege infra/sentry/ an gemäß Implementation-Steps 1-6, committe atomar pro Step"
  • Backup-Implementation: separater general-purpose-Subagent für Step 7 (Backup-Container — eigene Schicht, eigener Commit)
  • Doku: separater general-purpose-Subagent für Steps 8-10 (Doku-Files schreiben)
  • Verification: general-purpose-Subagent mit Brief: "Smoke-Tests auf Staging gemäß Test-Requirements ausführen, Output protokollieren"
  • Code-Review: gsd-code-reviewer nach Task-Completion

Staging-Deployment-Steps

  1. Branch agent/phase-02-sentry-selfhost erstellen + pushen
  2. Im Coolify-UI auf Hetzner-Server neuen Service "Docker Compose" anlegen:
    • Repo: dieser Branch
    • Compose-Path: infra/sentry/docker-compose.yml + docker-compose.override.yml
    • Domain: sentry.<staging-domain>
    • Environment-Variables aus .env.example (Werte aus Coolify-Vault)
  3. Erstes Deployment triggert install.sh-Wrapper → Postgres-Migrations + Admin-User-Setup laufen (dauert ~5-10 Minuten)
  4. Smoke-Test:
    # auf Hetzner via SSH
    docker compose -f /data/coolify/services/<id>/docker-compose.yml exec web \
      sentry shell -c "from sentry.models import User; print(User.objects.all())"
    # Erwartung: Admin-User existiert
    
  5. Lokal:
    SENTRY_DSN="<dsn-aus-ui>" npx sentry-cli send-event -m "phase-02 smoke from $(hostname)"
    # Erwartung: in der UI sichtbar binnen 30s
    
  6. OTLP-Smoke:
    curl -X POST "https://sentry.<staging-domain>/api/<project_id>/otlp/v1/traces" \
      -H "Content-Type: application/json" \
      -H "X-Sentry-Auth: Sentry sentry_key=<public_key>" \
      -d '{"resourceSpans":[]}'
    # Erwartung: 202
    
  7. Backup-Verifikation nach 24h:
    aws s3 ls s3://<backup-bucket>/sentry/$(date +%Y-%m-%d)/
    # Erwartung: postgres-dump.sql.gz + clickhouse-snapshot.tar.gz
    
  8. Falls alles grün: PR → main mergen; Production-Deployment (eigener Hetzner oder zweiter Stack) erst nach erfolgreichem Backup-Restore-Drill.

Deviations from Plan

  • No vendored upstream snapshot. The detail-MD's Step 2 originally proposed a vendored snapshot of getsentry/self-hosted v25.x. We deliberately chose NOT to vendor the ~70-service upstream tree because (a) the resulting merge-conflict surface on every minor upgrade is large, (b) Coolify's git-pull-on-deploy gets slow, (c) the attribution surface grows. Instead, install.sh clones the upstream repo at the pinned tag at deploy time, and only our overlay files (compose-override + sentry.conf.py + relay/config.yml + backup/) live in the repo. infra/sentry/upstream/.gitkeep keeps the target directory present; infra/sentry/.gitignore excludes the cloned upstream/self-hosted/ tree. Rationale lives in infra/sentry/UPSTREAM.md.
  • Pinned tag is 25.12.1, not 25.12.0. 25.12.0 shipped a known snuba-migration bug; 25.12.1 is the hotfix. We trail one major behind the latest (26.x exists but is not LTS-qualified for us yet).
  • Hardware reality. Detail-MD assumed 16 GB RAM; staging host has now been upgraded to 32 GB / 4 vCPU / 75 GB disk (per STAGING-ACCESS.md). The volume-sizing guidance in docs/observability/sentry-selfhost.md is calibrated to this footprint.
  • GlitchTip image pin. Detail-MD did not specify a version; we pinned glitchtip/glitchtip:v6.1.6 and used valkey/valkey:7-alpine (Valkey is the Redis fork that GlitchTip 6.x officially supports — keeps us off the increasingly-restrictive upstream redis license).
  • Pre-push i18n baseline. A separate, unrelated commit (faaae54e) raises the i18n-hardcoded-strings baseline for user-facing-prop-literal from 0 to 11. This drift was introduced by the magic-link merge that landed earlier on 2026-05-17 and was blocking ALL pushes to main. The proper i18n migration of those strings remains follow-up work for Phase 01 #19/#20.
  • Step 11 (Coolify deploy + smoke-test) intentionally remains pending — this commit lands artifacts only; the staging-deploy is the next operator-driven step.

Progress Log

  • 2026-05-17 — ed740e9e — scaffold infra/sentry directory (README + .gitignore)
  • 2026-05-17 — faaae54e — (unblocker) raise i18n user-facing-prop-literal floor 0 → 11 so pushes to main work post-merge-drift
  • 2026-05-17 — 547f6acb — pin upstream getsentry/self-hosted v25.12.1 reference (UPSTREAM.md + upstream/.gitkeep)
  • 2026-05-17 — f5e84ef3 — add compose overlay with hardened config (docker-compose.override.yml + sentry/sentry.conf.py)
  • 2026-05-17 — ccc71f3f — add install wrapper script (install.sh + .env.example)
  • 2026-05-17 — d1454aa1 — add traefik labels for sentry subdomain (traefik.labels.yml)
  • 2026-05-17 — 350d22b5 — enable OTLP ingest in relay (relay/config.yml)
  • 2026-05-17 — 9e2ccc25 — add daily backup container (backup/Dockerfile + backup.sh + crontab)
  • 2026-05-17 — 280ce309 — document update + maintenance strategy (docs/observability/sentry-selfhost.md sections 1-9)
  • 2026-05-17 — 43d4d9ab — add hardening checklist (sentry-selfhost.md ## Hardening Checklist)
  • 2026-05-17 — 61e5a0a9 — document glitchtip light alternative (docs/observability/sentry-glitchtip-alternative.md)
  • 2026-05-17 02:15 — deploy — sentry self-hosted 25.12.1 provisioned on staging at https://sentry.boiler.saas-ctrl.com (DNS A-record 45822dc7204dd84ecfc77789d8695f75, Traefik routing via Coolify-Proxy with Let's Encrypt cert). 3 projects created (boilerplate-api id=2, boilerplate-worker id=3, boilerplate-web id=4) under org sentry. Admin user admin@tool-ctrl.de seeded. Smoke test green: sentry-cli send-event ingested into project 2 (event id 7b8093fc-0797-4c7c-b07b-4aeec4496407), envelope endpoint /api/2/envelope/ returns 200, /auth/register/ 302→/auth/login/sentry/ (public signup OFF). Stack runs at /opt/sentry-stack/self-hosted/ (NOT under Coolify management). DSNs + secrets stored locally in ~/.cli-tokens/boilerplate-staging-env. Caveat: OTLP-HTTP at /api/<id>/otlp/v1/traces returns CSRF/403 — upstream nginx.conf in 25.12.1 only routes /api/0/relays/ to relay, not OTLP paths. Follow-up in task #33 (OpenTelemetry) needs to either patch the nginx route or send OTLP directly to relay's internal port via the Sentry SDK's auto-transport. Regular Sentry SDK ingestion via /api/<id>/envelope/ works.

Lessons Learned

(Wird am Task-Ende ergänzt — typische Stolperfallen: ClickHouse-OOM auf < 12 GB RAM, Kafka-Topic-Lag bei Backup-Restore, Symbolicator-Cache-Größe.)