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 DSN02-observability/sentry-wiring-web.md— braucht funktionierende DSN + Project02-observability/sentry-ci-integration.md— braucht funktionierendes Org/Project fürsentry-cli02-observability/opentelemetry.md— Relay-OTLP-Endpoint muss in Sentry-Compose freigeschaltet sein03-scaling-infrastructure/*— alle Refactorings wollen Sentry-Sichtbarkeit09-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
-
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
- Files:
-
Pinning & Sub-Repo —
getsentry/self-hostedv25.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
- Files:
-
Compose-Overlay —
docker-compose.override.ymlmit 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']=Falseinsentry/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
- Network
-
install.sh-Wrapper — eigener Wrapper
infra/sentry/install.sh, der das upstream-install.shmit unseren Defaults aufruft und am Ende einen Superuser anlegt (SENTRY_ADMIN_EMAIL/SENTRY_ADMIN_PASSWORDaus Env).- Files:
infra/sentry/install.sh,infra/sentry/.env.example - Commit:
02-observability/sentry-selfhost: add install wrapper script
- Files:
-
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
- Files:
-
OTLP-Ingest aktivieren —
relay/config.ymlso anpassen, dass Relay einen OTLP-HTTP-Endpoint (/api/{project}/integration/otlp/v1/traces) annimmt. Wird inopentelemetry.mdals Sender konsumiert.- Files:
infra/sentry/relay/config.yml - Commit:
02-observability/sentry-selfhost: enable OTLP ingest in relay
- Files:
-
Backup-Strategie — täglicher Cron-Container, der:
postgres-dumpmitpg_dump --format=custom→ S3-Upload viarcloneoderaws s3 cp- Clickhouse-Snapshot via
clickhouse-backup→ S3-Upload - Sentry-Encryption-Key (
SENTRY_SECRET_KEYin.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
-
Update-Strategie dokumentieren —
docs/observability/sentry-selfhost.mdmit:- Wie man von v25.x.a auf v25.x.b geht (
./install.shre-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
- Wie man von v25.x.a auf v25.x.b geht (
-
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
-
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.mdmit 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
- Files:
-
Coolify-Deployment + Smoke-Test — Stack auf Staging deployen, manuell Test-Event von lokalem
sentry-cli send-eventtriggern, in der UI prüfen.- Files: keine (Operations-Schritt, wird in
## Progress Logfestgehalten) - Commit: keiner (Deployment, kein Code)
- Files: keine (Operations-Schritt, wird in
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/tracesmit 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)
- Sentry-Web-UI erreichbar unter
- Integration-Test (Skript):
infra/sentry/scripts/smoke.shder 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.mdenthält: Deploy-Schritte, Update-Strategie, Backup/Restore-Drill, Hardening-Checkliste, Ressourcen-Anforderungendocs/observability/sentry-glitchtip-alternative.mdmit Light-Compose und DSN-Kompatibilitäts-Notiz.env.exampleenthältSENTRY_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 typecheckgrü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 revertder entsprechenden Commits — Coolify re-deployed dann den vorherigen Stand. Hetzner-Server-Disk-Quota beachten (alte Volumes manuelldocker volume rmfalls Platzmangel)
Security Notes
SENTRY_SECRET_KEYist 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_dumpmitgpg-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: "Legeinfra/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-reviewernach Task-Completion
Staging-Deployment-Steps
- Branch
agent/phase-02-sentry-selfhosterstellen + pushen - 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)
- Erstes Deployment triggert
install.sh-Wrapper → Postgres-Migrations + Admin-User-Setup laufen (dauert ~5-10 Minuten) - 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 - Lokal:
SENTRY_DSN="<dsn-aus-ui>" npx sentry-cli send-event -m "phase-02 smoke from $(hostname)" # Erwartung: in der UI sichtbar binnen 30s - 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 - Backup-Verifikation nach 24h:
aws s3 ls s3://<backup-bucket>/sentry/$(date +%Y-%m-%d)/ # Erwartung: postgres-dump.sql.gz + clickhouse-snapshot.tar.gz - 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-hostedv25.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.shclones 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/.gitkeepkeeps the target directory present;infra/sentry/.gitignoreexcludes the clonedupstream/self-hosted/tree. Rationale lives ininfra/sentry/UPSTREAM.md. - Pinned tag is 25.12.1, not 25.12.0.
25.12.0shipped a known snuba-migration bug;25.12.1is 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.mdis calibrated to this footprint. - GlitchTip image pin. Detail-MD did not specify a version; we pinned
glitchtip/glitchtip:v6.1.6and usedvalkey/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 foruser-facing-prop-literalfrom 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-apiid=2,boilerplate-workerid=3,boilerplate-webid=4) under orgsentry. Admin useradmin@tool-ctrl.deseeded. Smoke test green:sentry-cli send-eventingested into project 2 (event id7b8093fc-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/tracesreturns CSRF/403 — upstreamnginx.confin 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.)