Version: Current
Historical release snapshot. These docs are frozen. Read current docs.
Self-hosted Sentry
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.
What runs
Section titled “What runs”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-onlySENTRY_EVENT_RETENTION_DAYS=30SENTRY_BIND=127.0.0.1:9000SENTRY_TASKWORKER_CONCURRENCY=2SENTRY_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.
Exposure
Section titled “Exposure”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.
Error 1010
Section titled “Error 1010”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.
Access
Section titled “Access”| 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:
sudo install -m 600 -o <user> -g <group> \ /opt/sentry/dsn/fox.dsn /path/for/the/service/fox.dsnRotate the admin password through the UI. The shell alternative puts the password in history, so prefer the UI.
Projects
Section titled “Projects”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.
Operating it
Section titled “Operating it”- 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 pullandup -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.
Wiring a Worker
Section titled “Wiring a Worker”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.
Known gaps
Section titled “Known gaps”mail.backend: dummy, so there is no password reset, no verification email.- No Access policy on the UI; internet-facing behind Sentry login only.
- The org’s admin account is a single operator identity on the host.
- Downgrades across a schema migration are not supported.