TL;DR
- We run exception-only ingest on a $5-12 class VPS: two containers (app + Postgres 16), official Sentry SDKs via DSN swap.
- We ruled out full self-hosted Sentry (dozens of containers) and a SaaS bill for errors-only work.
- Postgres stays off the host port. TLS edge is a separate recipe (Caddy post); this post is cost + footprint + constraints.
I run Epure exception ingest on the same class of box I use for side projects: one cheap VPS, one engineer, no Kubernetes. The job is narrow. Catch production exceptions, group them, keep Postgres off the public internet. Not APM. Not session replay. Not a 65-container Sentry clone on a droplet that costs less than lunch.
Here are the notes from that choice: constraints, what we ruled out, a minimal compose sketch, and the failure checklist we hit while wiring it.
What constraints did we accept?
$5-12/month VPS, one operator, official Sentry SDKs pointed at our own DSN. Errors only.
Concrete box:
| Constraint | What we mean |
|---|---|
| Budget | $5-12 class VPS (1-2 vCPU, modest RAM). Not a managed cluster. |
| Headcount | One engineer who can SSH, edit compose, read logs. |
| Client path | Official Sentry SDKs (@sentry/node, sentry-go, etc.). DSN swap. No custom client. |
| Scope | Exceptions / issues. No APM, replay, profiling, logs, or mobile suite. |
If you need the full Sentry product surface, this post is the wrong shape. Keep SaaS or self-host the big stack on hardware sized for it.
What did we rule out?
Full self-hosted Sentry (~65 containers) and paying SaaS just for exception ingest.
| Option | Why it lost for this job |
|---|---|
| Self-hosted Sentry | Real product. Also a real fleet (Kafka, ClickHouse, Redis, workers, …). Fine on a dedicated host. Painful on a $5 box when you only need errors. |
| SaaS Sentry (exceptions-only) | Works. You pay for a bill and a vendor boundary you may not want for a side project or a privacy-sensitive ingest path. |
| GlitchTip | Fair lighter alternative. Open source, Sentry-protocol-ish, smaller than full Sentry. We still chose Epure because we are building it and want Rust + Postgres RLS for our threat model. A GlitchTip reader can still use the VPS constraints below. |
We are not saying "kill Sentry." We are saying: if the job is exceptions on a cheap VPS, the 65-container install is the wrong unit of deployment.
Epure is exception-only. Official SDKs + DSN. Not "100% Sentry compatible." If your checklist includes replay, APM, or profiling, stop here and pick a suite that actually ships those.
Mental model before compose
Internet → TLS edge (Caddy :443) → app:8080 (Docker net)
→ postgres:5432 (Docker net only, NO host publish)
Two containers for the product. Proxy in front for HTTPS. Database never bound to 0.0.0.0:5432 on the host.
For the TLS edge recipe (Caddyfile, 80/443, Postgres unpublished), use the live post: Put self-hosted exception tracking behind Caddy. This article does not rehash every Caddy stanza.
Minimal two-container compose
App + postgres:16. Publish the app (or leave it for Caddy on the Docker network). Do not publish Postgres to the host.
Sketch shaped like production (passwords via .env, no example defaults on a public URL):
# docker-compose.errors.yml (sketch; pin your own image tags)
services:
app:
image: ghcr.io/epure-sh/epure:latest # pin a release tag in prod
restart: unless-stopped
env_file: [.env]
environment:
DATABASE_URL: postgres://epure:${POSTGRES_PASSWORD}@postgres:5432/epure
EPURE_PUBLIC_URL: https://errors.example.com
EPURE_CORS_ORIGINS: https://app.example.com
depends_on:
postgres:
condition: service_healthy
ports:
- "8080:8080" # or omit and let Caddy reach app:8080 on the Docker net
networks: [web]
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: epure
POSTGRES_USER: epure
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U epure -d epure"]
interval: 5s
timeout: 5s
retries: 10
# NO ports: stay on the Docker network only
networks: [web]
volumes:
postgres_data:
networks:
web:
Bring it up:
docker compose -f docker-compose.errors.yml up -d
curl -sS http://127.0.0.1:8080/health
Expected:
{"status":"ok"}
Then put Caddy (or nginx) on 80/443 in front of 127.0.0.1:8080, point DNS, register, copy a project DSN, and captureException from an official SDK.
Canonical install steps (prod overlay, ./configure --prod, PaaS templates): Epure self-hosting installation.
Expected footprint (honest)
Qualitative only until we publish measured numbers.
| Stack | Container count (order of magnitude) | Fit on $5-12 VPS? |
|---|---|---|
| Full self-hosted Sentry | Dozens (~65-class fleet with deps) | Usually no, not for errors-only |
| GlitchTip | Small multi-service compose | Often yes (lighter than full Sentry) |
| Epure (what we run) | 2 (app + Postgres 16) | Designed for this class of box |
We will not invent RAM figures. If you measure, paste docker stats and free -h in the comments. We will update this section with real numbers when we have them on a named plan.
Failure checklist
-
Postgres published to the host (
5432:5432"just for debugging"). Scanners find it. Drop theports:block; reach DB only aspostgres:5432on the Compose network. - Laptop compose used as prod. Example passwords + localhost URL rules do not belong on a public hostname. Use the production overlay / env template from the docs.
- No TLS, cookies broken. Secure session cookies need HTTPS. Finish the Caddy edge before you debug "login flaps."
-
Health is green, DSN host is wrong.
/healthon localhost is not the same as the public origin in the DSN. MatchEPURE_PUBLIC_URL/ proxy host to what the SDK posts to. - CORS blocks browser SDKs. SPA origin missing from allowlist. Fix origins before blaming the SDK (see our CORS post if you already hit this).
- OOM while "just trying self-hosted Sentry." The fleet, not your app. Errors-only on two containers is the cheap-VPS shape; full Sentry wants a bigger host.
- Expecting APM / replay / profiling. Exception-only stacks will not grow those features because you wished hard. Pick the product that matches the checklist.
If the two-container shape matches what you need, follow the install doc once, put TLS in front, swap the DSN on an official SDK, and confirm a test exception lands: self-hosting installation.
Discussion
What do you run on a cheap VPS for errors, and what blew the RAM first?
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.