DEV Community

Cover image for Error monitoring on a $5 VPS
Amorizz
Amorizz

Posted on

Error monitoring on a $5 VPS

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)
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

Bring it up:

docker compose -f docker-compose.errors.yml up -d
curl -sS http://127.0.0.1:8080/health
Enter fullscreen mode Exit fullscreen mode

Expected:

{"status":"ok"}
Enter fullscreen mode Exit fullscreen mode

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

  1. Postgres published to the host (5432:5432 "just for debugging"). Scanners find it. Drop the ports: block; reach DB only as postgres:5432 on the Compose network.
  2. 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.
  3. No TLS, cookies broken. Secure session cookies need HTTPS. Finish the Caddy edge before you debug "login flaps."
  4. Health is green, DSN host is wrong. /health on localhost is not the same as the public origin in the DSN. Match EPURE_PUBLIC_URL / proxy host to what the SDK posts to.
  5. CORS blocks browser SDKs. SPA origin missing from allowlist. Fix origins before blaming the SDK (see our CORS post if you already hit this).
  6. 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.
  7. 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.