DEV Community

Cover image for Point the official Sentry SDK at self-hosted ingest (DSN only)
Amorizz
Amorizz

Posted on Originally published at epure.sh

Point the official Sentry SDK at self-hosted ingest (DSN only)

You don’t need a forked Sentry SDK. Keep @sentry/browser or @sentry/node, change only dsn, then prove /health → HTTP 202 → Issues. Two containers (app + Postgres 16); leave replay, traces, and profiles at sample rate 0 — those envelope items are out of scope.

What you will do

  1. Clone and docker compose up
  2. Confirm GET /health{"status":"ok"}
  3. Install a pinned official SDK (7.120.0)
  4. Sentry.init with your DSN and zeroed unused sample rates
  5. captureException and expect ingest 202, then an Issues row
  6. Optional: raw store / envelope curls if the SDK path is noisy

Two containers, one health check

Clone the public tree and start compose from the repo root:

git clone https://github.com/epure-sh/epure.git
cd epure
docker compose up
Enter fullscreen mode Exit fullscreen mode

Two services: the app image and postgres:16-alpine. No Redis. No separate worker fleet on this laptop path.

If port 8080 is taken:

cp .env.example .env
# set EPURE_PORT and EPURE_PUBLIC_URL together, then recreate the app container
Enter fullscreen mode Exit fullscreen mode

Wait for health:

curl -sS http://localhost:8080/health
Enter fullscreen mode Exit fullscreen mode

Expected body:

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

Open http://localhost:8080. Register in the UI, or for local smoke only (never production):

./scripts/seed-dev.sh
Enter fullscreen mode Exit fullscreen mode

Copy the DSN (only thing that changes)

From the dashboard: Settings → SDK connection (or the setup checklist). Create a key if none exists. Shape:

http://{public_key}@localhost:8080/{project_id}
Enter fullscreen mode Exit fullscreen mode

Migrating from Sentry SaaS? Keep the same init options. Replace only dsn.

Init once, throw once

Pin the fixtures the public docs exercise:

# browser SPA
npm install @sentry/browser@7.120.0

# or Node API / worker
npm install @sentry/node@7.120.0
Enter fullscreen mode Exit fullscreen mode

Browser example:

import * as Sentry from "@sentry/browser";

Sentry.init({
  dsn: "http://YOUR_PUBLIC_KEY@localhost:8080/YOUR_PROJECT_ID",
  environment: "local",
  release: "my-app@0.0.0",
  tracesSampleRate: 0,
  profilesSampleRate: 0,
  replaysSessionSampleRate: 0,
  replaysOnErrorSampleRate: 0,
});

Sentry.captureException(new Error("Epure quickstart test"));
Enter fullscreen mode Exit fullscreen mode

Node is the same shape with @sentry/node. Set unused sample rates to 0. Transactions, session replay, and profiles are discarded on an exception-only host — zeroing them stops the SDK from sending work you will never see in Issues.

Confirm 202, then Issues

Ingest accepts before the Issues row appears. A successful SDK or store/envelope POST returns HTTP 202 with a body like:

{ "id": "660e8400-e29b-41d4-a716-446655440001" }
Enter fullscreen mode Exit fullscreen mode

Refresh http://localhost:8080. Match the environment filter chip to Sentry.init (local in the example). You want an unresolved issue for Error / Epure quickstart test.

Empty feed copy looks like 0 UNRESOLVED EXCEPTIONS. That alone is not proof of failure — see the checklist below.

Optional: curl without an SDK

Useful when you need to separate “SDK wiring” from “host ingest.” Seed IDs below match ./scripts/seed-dev.sh in the public docs. Change host if EPURE_PORT is not 8080.

Legacy store JSON:

curl -sS -D - -o /tmp/epure-ingest.json -X POST \
  "http://localhost:8080/api/550e8400-e29b-41d4-a716-446655440000/store/" \
  -H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=a1b2c3d4e5f6g7h8i9j0, sentry_secret=supersecretdevkey" \
  -H "Content-Type: application/json" \
  -d '{
  "platform": "javascript",
  "exception": {
    "values": [{ "type": "Error", "value": "Epure try-it" }]
  }
}'
Enter fullscreen mode Exit fullscreen mode

Expect HTTP/1.1 202 Accepted and an id in the JSON body.

Envelope path (same auth, binary body from the repo):

curl -sS -D - -o /tmp/epure-envelope.json -X POST \
  "http://localhost:8080/api/550e8400-e29b-41d4-a716-446655440000/envelope/" \
  -H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=a1b2c3d4e5f6g7h8i9j0, sentry_secret=supersecretdevkey" \
  -H "Content-Type: application/x-sentry-envelope" \
  --data-binary @fixtures/sentry/browser/envelope.txt
Enter fullscreen mode Exit fullscreen mode

Endpoints that matter:

Method Path
POST /api/{project_id}/store/
POST /api/{project_id}/envelope/
GET /health

When 202 is a lie

202 means auth + schema accepted. It is non-blocking. It does not mean “row visible in Issues yet.”

Checklist when the feed stays empty after a 202:

  1. Wait, then refresh — accept is ahead of the worker. One immediate refresh is not final.
  2. Environment chip — UI filter must match environment in Sentry.init (local in the examples).
  3. Project / DSN mismatch — open the project that owns the key you put in dsn.
  4. Wrong host or portEPURE_PUBLIC_URL / EPURE_PORT drift after a .env change without recreating the app container.
  5. SDK still talking to SaaS — leftover dsn, or a second Sentry.init later in boot.

If store curl returns 202 but the SDK path does not, debug the client. If both miss 202, debug the host (docker compose logs on the app service, health, DSN key).

What this host is not

Exception-only lane. Do not expect APM, session replay, profiling, log product, or mobile SDKs to “just work” because the DSN looks familiar. JS/TS source maps are a separate verify step (upload + prove demangled frames) — out of scope here.

Do not write “100% Sentry compatible” in a PR description or a README badge. Honest subset: official SDK, DSN swap, exceptions, envelope/store.

FAQ

Does Sentry JS 8.x work?

Often yes. CI fixtures are 7.120.0. On v8+, set enableTracing: false when the option exists, and keep traces / profiles / replay sample rates at 0. Pin 7.120.0 if you want the same line the docs exercise. Confirm with a real throw, not only a green install.

Can I use Python or curl only?

Yes. Envelope and store accept official Sentry SDKs and raw JSON. Store is the smallest wire path. Phase 1 demangle stays JS/TS only, so frames stay as sent for other runtimes.

Do I need Redis?

No. Two containers for this path: app + Postgres 16.

Laptop compose in production?

No. Use the production overlay and a real .env from .env.production.example. Seed script and example DB passwords are local smoke only.

Canonical docs

If those URLs 404 someday, the commands above still stand: health JSON, store/envelope 202, DSN host swap, sample rates at 0.

Discussion

After ingest returns 202 but Issues stays empty — do you check the environment chip, project/DSN match, or worker lag first?

Top comments (0)