Skip to content
Version: Current

Self-hosted Sentry

Unverified2026-11-05. repository reference/snapshot reviewed 2026-11-05; verify current behavior before using as live guidance.

Scope: the host, the self-hosted instance on core: what runs, how it is exposed, the DSN files, backup and upgrade. This page is the rendered copy of the internal host runbook and is authoritative for the instance. The harness client and its config schema are canonical in kivox/ops (harness/sentry.mjs; operator doc docs/sentry.md); the per-service SDK plan is separate and not repeated here.

Status: deployed and verified 2026-10-20. Pinned release 26.10.0. Host: core (Fedora, 12 cores, 32 GB RAM, Docker 29.x, Compose v5.x, tailnet 100.64.0.4). Install root: /opt/sentry: the official getsentry/self-hosted checkout, detached at the release tag. Public surface: https://sentry.tcp.systems, with DSN ingestion on the same host.

Nothing on the edge host was changed for this: no reverse-proxy edit, no headscale edit, no harness unit. Sentry is reached through a dedicated Cloudflare Tunnel that terminates on core, not through the shared tunnel and not through the proxy allowlist.

COMPOSE_PROFILES=errors-only. That is the official reduced profile: errors and attachments only: no profiling, replays, uptime, cron or launchpad consumers. It needs roughly 7 GB; feature-complete needs 14-16 GB and would compete with the desktop, memory services and the embedder.

Pinned images: Sentry’s own services all at 26.10.0; ClickHouse, Postgres, Kafka, Valkey and SeaweedFS carry their own pinned tags or digests in the compose file. Host-level settings in /opt/sentry/.env:

COMPOSE_PROFILES=errors-only
SENTRY_EVENT_RETENTION_DAYS=30
SENTRY_BIND=127.0.0.1:9000
SENTRY_TASKWORKER_CONCURRENCY=2

SENTRY_BIND is loopback only. The public listener is cloudflared, not Docker.

Volume Contents
sentry-postgres users, organizations, projects, teams, project keys, issue metadata
sentry-clickhouse event tables
sentry-seaweedfs event nodestore payloads and attachments
sentry-kafka in-flight ingest buffer (transient, kept for restart safety)
sentry-redis cache and rate-limit state (transient)
sentry-data filestore and runtime data

docker compose down without -v keeps the volumes; a restart re-attaches them.

Public URL prefix, in the install’s sentry/config.yml:

system.url-prefix: 'https://sentry.tcp.systems'
mail.backend: 'dummy'

Mail is deliberately dummy. Password reset and email verification do not work until an SMTP relay is added. That is recorded as a gap, not hidden.

core is a Headscale client. Tailscale Funnel is not available on a Headscale tailnet. Funnel needs Tailscale-controlled DNS and certificate issuance, so the public edge is a second, independent Cloudflare Tunnel:

Item Value
Tunnel name sentry
DNS sentry.tcp.systems CNAME to the tunnel, proxied
Ingress sentry.tcp.systems → http://127.0.0.1:9000, then http_status:404
Connector cloudflared-sentry.service, four connections, healthy
cloudflared pinned, no autoupdate
Config /etc/cloudflared/sentry.yml, mode 600
Credentials tunnel credential JSON, /etc/cloudflared/, mode 600

The shared dsh tunnel is untouched. No Cloudflare Access policy is attached: Workers must be able to POST envelopes without an Access service token. The UI is therefore internet-facing and protected only by Sentry’s own login.

Add Access only together with a service token for ingestion. An Access app without that exception breaks DSNs.

Cloudflare’s Browser Integrity Check rejects clients with no User-Agent (HTTP 403, error code: 1010) before the request reaches Sentry. All official SDKs set one; a hand-written probe must too.

Client Result
urllib default UA 403, error 1010
sentry.python/2.0.0 200
sentry.javascript.cloudflare/11.8.0 200

Do not disable BIC globally to “fix” this. Give a custom ingestion client a Sentry-style UA.

What Where Mode
Web UI / API https://sentry.tcp.systems HTTPS via Cloudflare
Admin login operator account on the host password file /root/.sentry-admin, 600
DSN files /opt/sentry/dsn/<project>.dsn 600, directory 700
All DSNs and project metadata /opt/sentry/dsn/all.json 600
Tunnel credentials /etc/cloudflared/ 600

A DSN is an ingestion key, not an admin credential, but it is still configuration: it is not committed and not printed. Read one only when wiring a service:

Terminal window
sudo install -m 600 -o <user> -g <group> \
/opt/sentry/dsn/fox.dsn /path/for/the/service/fox.dsn

Rotate the admin password through the UI. The shell alternative puts the password in history, so prefer the UI.

Organization tcpsystems, one team, six projects: acumen, radar, fox, foxd, floki, harness. Each has a DSN file under /opt/sentry/dsn/; the values are not reproduced anywhere in this repository.

DSN shape:

https://<32-hex-public-key>@sentry.tcp.systems/<project-id>

The pre-existing release self-monitoring organization was left alone.

  • Restart: cd /opt/sentry && sudo docker compose up -d. Named volumes re-attach; no data is lost.
  • Upgrade: check out the new tag in the install tree, re-apply the local settings that are not in the release (bind, profile, retention, worker concurrency), then docker compose pull and up -d. Recreate the four custom keys after checkout.
  • Rollback: check out the previous tag and up -d; the volumes carry the schema forward, so downgrade across a migration is not supported. Restore a volume backup instead.
  • Retention: 30 days, set by SENTRY_EVENT_RETENTION_DAYS.
  • Backups: Postgres and ClickHouse dumps to the host backup directory, plus the install’s config and .env. Verify a restore into a throwaway database before trusting the backup.

Cloudflare Workers use @sentry/cloudflare. Initialize it in the Worker entry with the project’s DSN read from a secret binding, never from a committed file. Trigger a thrown error in a preview deployment and confirm the issue appears in tcpsystems → <project> → Issues before calling the integration done.

  1. mail.backend: dummy, so there is no password reset, no verification email.
  2. No Access policy on the UI; internet-facing behind Sentry login only.
  3. The org’s admin account is a single operator identity on the host.
  4. Downgrades across a schema migration are not supported.