Your App Is Lying to You
Your hobby project runs fine — until it doesn’t. You deploy to a VPS, it starts up, and you walk away feeling accomplished. Then three days later you scroll logs and find a wall of Python tracebacks from Tuesday at 2 AM when a user hit that one endpoint you forgot to test with empty input.
Congratulations. You’ve been flying blind.
stdout is not error tracking. Grepping logs is not error tracking. Silently swallowing exceptions in a try/except block with a pass is definitely not error tracking. Real error tracking means: the moment something breaks, you know exactly what broke, where, with what data, and how many times. That’s Sentry.
The cloud version is great. It’s also $26/month before you start adding teammates. If you’re running five personal projects, that math doesn’t work out. The self-hosted version exists, and it’s free — but it comes with its own tax.
Let’s talk about both.
What Sentry Self-Hosted Actually Is
The getsentry/self-hosted repo is an install.sh script that pulls down a Docker Compose stack with roughly 30 services. That’s not an exaggeration.
Here’s a sample of what you’re dealing with:
- Kafka — event ingestion queue
- ClickHouse — event storage (the big one — disk hog)
- PostgreSQL — application data, projects, users, alerts
- Redis — caching, task queues
- Celery workers — background task processing
- Snuba — query layer over ClickHouse
- Relay — inbound event processing and filtering
- Nginx — reverse proxy for the web UI
- symbolicator — stack trace symbolication (this is the one that processes source maps)
When it works, it’s genuinely impressive. You get the full Sentry product — error grouping, performance monitoring, releases, source maps, alert rules, the whole UI. When something breaks, you’re debugging a distributed system on a Friday evening.
Resource Reality
Sentry’s own docs say 8 GB RAM minimum. That’s technically true — it’ll start. In practice, 8 GB is the floor where it runs okay with no load. If you’re actually sending errors to it, or you have more than one project, plan for 16 GB minimum. ClickHouse alone will eat memory.
Disk is the other one. ClickHouse stores raw events, and it stores a lot of them. Start with 50 GB of dedicated disk, separate from your OS disk if possible. Enable data retention limits early — otherwise ClickHouse grows without mercy.
CPU: 4 cores minimum, 8 comfortable.
If you don’t have this lying around, Sentry self-hosted is not for your $5 VPS.
The License Situation
Sentry switched to the Functional Source License (FSL) in October 2023. The short version for self-hosters:
- You can run it for your own internal use. Free.
- You cannot offer it as a service to others (i.e., you can’t run it and sell access to customers).
- After two years, the code converts to Apache 2.0.
For personal homelab use and small teams running it internally, FSL is fine. You’re not selling anything. Don’t spend more than five minutes worrying about it.
Installing It
Prerequisites
# Docker + Docker Compose v2 installed# At least 16GB RAM, 50GB+ disk# Open port 9000 (or whatever you proxy to)
git clone https://github.com/getsentry/self-hosted.gitcd self-hostedThe install script handles the rest — generating configs, pulling images, running migrations, and creating the initial admin user.
sudo ./install.shThis takes a while. Kafka and ClickHouse images are large. On a decent connection, budget 15-20 minutes.
When it finishes, you’ll be prompted to create an admin account. Do it. Then:
docker compose up -dThe UI comes up at http://localhost:9000. Put a reverse proxy in front of it — Caddy or Nginx — and enable HTTPS before you do anything else.
Caddy Reverse Proxy
sentry.yourdomain.com { reverse_proxy localhost:9000}That’s it. Caddy handles certs automatically. Change your SENTRY_BIND in .env if port 9000 conflicts with something else.
The .env File
The install script generates a .env file in the self-hosted/ directory. Key things to set:
COMPOSE_PROJECT_NAME=sentrySENTRY_BIND=127.0.0.1:9000
# Mail settings — wire up your SMTP relay hereSENTRY_EMAIL_HOST=smtp.yourprovider.comSENTRY_EMAIL_PORT=587SENTRY_EMAIL_USER=alerts@yourdomain.comSENTRY_EMAIL_PASSWORD=yourpasswordSENTRY_SERVER_EMAIL=alerts@yourdomain.comSENTRY_EMAIL_USE_TLS=trueWithout mail configured, you can’t reset passwords or receive alert emails.
Setting Up Your First Project
Log into the UI, create an organization (call it whatever), then:
- Projects → New Project
- Pick your platform (Python, Node.js, React, etc.)
- Copy the DSN — it looks like
https://<key>@sentry.yourdomain.com/<project-id>
That DSN is the only thing your application needs.
SDK Setup
Python
pip install sentry-sdkimport sentry_sdk
sentry_sdk.init( dsn="https://yourkey@sentry.yourdomain.com/1", # Capture 100% of errors, 10% of transactions traces_sample_rate=0.1, # Attach request data, user context, etc. send_default_pii=True, environment="production", release="myapp@1.4.2",)That’s the entire setup for a Python app. Sentry monkey-patches exception handling automatically — any unhandled exception gets captured and sent.
If you use Flask or FastAPI, add the integration:
import sentry_sdkfrom sentry_sdk.integrations.flask import FlaskIntegrationfrom flask import Flask
sentry_sdk.init( dsn="https://yourkey@sentry.yourdomain.com/1", integrations=[FlaskIntegration()], traces_sample_rate=0.1,)
app = Flask(__name__)Django has its own integration, same pattern. Celery tasks, SQLAlchemy queries, Redis calls — they all get wrapped automatically when you add the relevant integration.
JavaScript / Node
npm install @sentry/node# or for browsernpm install @sentry/browserconst Sentry = require("@sentry/node");
Sentry.init({ dsn: "https://yourkey@sentry.yourdomain.com/1", tracesSampleRate: 0.1, environment: process.env.NODE_ENV, release: process.env.APP_VERSION,});For React:
import * as Sentry from "@sentry/react";
Sentry.init({ dsn: "https://yourkey@sentry.yourdomain.com/1", integrations: [ Sentry.browserTracingIntegration(), Sentry.replayIntegration({ maskAllText: false, blockAllMedia: false, }), ], tracesSampleRate: 0.1, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0,});Session replay is the premium feature that feels like magic — it records what the user was doing when they triggered the error, so you can watch a 30-second clip instead of guessing.
Source Maps (JavaScript)
If you’re shipping minified JavaScript, your stack traces look like:
TypeError: Cannot read properties of undefined (reading 'map') at n (main.abc123.js:1:42891)That’s useless. Source maps fix this. When you upload your build’s source maps to Sentry, those cryptic main.abc123.js:1:42891 references get translated back to your actual file and line number.
Upload During CI/CD
# Install the Sentry CLInpm install -g @sentry/cli
# Configure itexport SENTRY_URL=https://sentry.yourdomain.comexport SENTRY_AUTH_TOKEN=your_api_tokenexport SENTRY_ORG=your-org-slugexport SENTRY_PROJECT=your-project-slug
# After your build stepsentry-cli releases new "$RELEASE_VERSION"sentry-cli releases files "$RELEASE_VERSION" upload-sourcemaps ./dist --url-prefix "~/"sentry-cli releases finalize "$RELEASE_VERSION"Your API token comes from Sentry UI → Settings → Auth Tokens → Create New Token. Give it project:releases scope minimum.
The key thing is that release in your SDK init must match the release you created via CLI. They’re strings — use a git SHA or semantic version, consistently.
Performance Monitoring (Transactions)
Sentry can track how long your requests take, which database queries are slow, and where time is spent across services. This is the “performance” tab.
It’s genuinely useful. It’s also resource-intensive on the self-hosted side because transaction data goes through Kafka and ClickHouse like everything else — just more of it.
Honest take: set traces_sample_rate to 0.05–0.1 max. At 1.0 (every request), a busy app will feed Sentry faster than your ClickHouse can handle. Start low, bump it up if you want more data.
If you’re running a personal project with light traffic, 0.1 is plenty. If you just want errors and don’t care about performance tracing, set it to 0.0 and skip it entirely. Error tracking and performance monitoring are independent — you don’t need both.
Alerts That Actually Work
An error tracker without alerts is just a log viewer with a nicer UI. Sentry’s alert system is legitimately good.
Setting Up Slack Alerts
In Sentry: Settings → Integrations → Slack → Add to Slack.
Then create an alert rule: Alerts → Create Alert → Issues.
Sample rule: “Notify Slack when any new issue is seen for the first time.”
{ "name": "New Issues - Slack", "conditions": [ {"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"} ], "actions": [ { "id": "sentry.integrations.slack.notify_action.SlackNotifyServiceAction", "workspace": "YOUR_WORKSPACE_ID", "channel": "#alerts-dev", "tags": "" } ], "frequency": 30}For Discord, use the webhook integration — same concept, different integration.
Fingerprinting Rules
Sentry groups errors by fingerprint — by default it groups similar stack traces together. Sometimes it groups things that are actually different issues (same exception type, different root cause). You can override this.
In your project settings → Issue Grouping → Fingerprinting Rules:
# Group by message text, not just exception typemessage:*timeout* -> timeout-issues# Separate DB connection errors from query errorsstack.function:connect_to_database -> db-connection-failureThis takes five minutes to set up and saves you from one grouped issue that’s actually 12 different bugs.
Backup: Don’t Lose Your Event History
Sentry self-hosted has two data stores you need to back up:
PostgreSQL — your org settings, users, projects, alert rules, saved searches. Small, critical.
# From your self-hosted directorydocker compose exec -T postgres pg_dump -U postgres sentry | gzip > sentry_postgres_$(date +%Y%m%d).sql.gzClickHouse — raw event data. Large, important if you care about historical errors.
docker compose exec clickhouse clickhouse-client \ --query "BACKUP DATABASE sentry TO Disk('backups', 'sentry_$(date +%Y%m%d)')"ClickHouse backup requires the backup disk to be configured. Honestly: if you lose event history, you lose event history. The more critical backup is Postgres — that’s your config and grouping data. Events are useful, but they’re also ephemeral by nature.
Set a retention period in Sentry UI: Settings → Admin → General → Event Retention. 90 days is plenty for most personal projects. ClickHouse will thank you.
Lighter Alternatives (When Sentry Is Too Heavy)
Let’s be honest: not everyone has a 16 GB RAM server sitting around for error tracking. There are real alternatives.
GlitchTip
GlitchTip is the most mature drop-in alternative. It’s built on Django, PostgreSQL, and Redis — no Kafka, no ClickHouse. Much smaller footprint. It implements the Sentry SDK API, so your existing SDK setup works without changes.
version: "3.8"services: postgres: image: postgres:16 environment: POSTGRES_DB: glitchtip POSTGRES_USER: glitchtip POSTGRES_PASSWORD: changeme volumes: - postgres_data:/var/lib/postgresql/data
redis: image: redis:7-alpine
web: image: glitchtip/glitchtip:latest depends_on: - postgres - redis ports: - "8000:8000" environment: DATABASE_URL: postgresql://glitchtip:changeme@postgres/glitchtip REDIS_URL: redis://redis:6379/0 SECRET_KEY: your-secret-key-change-this EMAIL_URL: smtp://user:pass@smtp.host:587 GLITCHTIP_DOMAIN: https://glitchtip.yourdomain.com DEFAULT_FROM_EMAIL: alerts@yourdomain.com
worker: image: glitchtip/glitchtip:latest command: ./bin/run-celery-with-beat.sh depends_on: - postgres - redis environment: DATABASE_URL: postgresql://glitchtip:changeme@postgres/glitchtip REDIS_URL: redis://redis:6379/0 SECRET_KEY: your-secret-key-change-this
volumes: postgres_data:GlitchTip runs comfortably in 2–4 GB RAM. No Kafka. No ClickHouse. No 30-service compose stack. The tradeoff: no performance monitoring, no session replay, no source map symbolication. It’s pure error tracking — which is honestly what most people need.
Bugsink
Bugsink is even lighter — a single Django process, SQLite-by-default, Sentry API compatible. If you’re running it for one project and want something that starts in 30 seconds, Bugsink works. It’s not going to handle serious volume, but for personal projects it’s fine.
Highlight.io
Highlight.io is the full-stack option — errors, logs, session replay, performance. Self-hosted via Docker Compose, Sentry API compatible for error ingestion. More resources than GlitchTip but less than full Sentry, and you get session replay which Sentry OSS doesn’t include.
When to Just Pay for SaaS Sentry
Here’s the honest version of this conversation:
If you’re a small team shipping something real, the hosted Sentry free tier (5K errors/month) or the Team plan is probably the right call. You get:
- Zero infrastructure to maintain
- Automatic upgrades
- Kafka is someone else’s problem
- Actual support
Babysitting a Kafka cluster is not engineering leverage. If your team has two developers trying to ship features, spending an afternoon debugging why Celery workers are OOM-killed is not a good use of time.
Self-hosted Sentry makes sense when:
- You process more events than SaaS pricing makes sense for
- You have compliance/data residency requirements
- You already have the infrastructure and someone who likes maintaining it
- You’re the person reading this blog, and you enjoy this kind of thing
If you’re in that last category — welcome. Kafkais just another thing to learn.
The Bottom Line
Sentry self-hosted is genuinely powerful. The full product runs on your hardware, your data stays yours, and it costs nothing beyond compute. The install is straightforward; the operational weight is not trivial.
If you have 16 GB of RAM to spare and want the full experience — error grouping, performance traces, source maps, alert rules, session replay — run the official getsentry/self-hosted stack.
If you want 90% of the value at 20% of the cost, run GlitchTip. It’s Sentry-compatible, tiny, and you’ll have it running before dinner.
Either way: stop shipping apps that log errors to stdout and call it monitoring. Your Tuesday self deserves better than finding out Wednesday morning.