TL;DR
- Keep the official Sentry SDK. Change only
dsnto your Epure project. - Set traces, profiles, and replay sample rates to 0. Epure is exception-only.
- Throw one test error, expect ingest HTTP 202, then an Issues row. Know what you drop.
I already ship @sentry/node (and browser) against SaaS Sentry. This week I pointed the same init at our self-hosted ingest: same SDK, new DSN, one verified event. The bill and the data residency story were enough; I did not want a client rewrite.
About ten minutes of wall clock once the ingest host is healthy. That is DSN copy, init edit, one throw, Issues check. Not a stopwatch claim for compose, TLS, or CORS.
What do you need before the clock starts?
Ingest already up. Official Sentry SDK already in the app.
| Prerequisite | Done means |
|---|---|
| Epure host |
/health returns {"status":"ok"} on the public origin you will put in the DSN |
| Project + DSN | Dashboard → Settings → SDK connection (or Setup). Key created. |
| App SDK | You already call Sentry.init with a SaaS DSN |
| Scope match | You only need exceptions / issues, not APM, replay, profiling, logs, or mobile |
Host not up yet? Bring the two-container stack online first (Error monitoring on a $5 VPS), then come back. This post does not retell compose.
Timed steps: DSN swap and one event
Copy the project DSN, replace only dsn, zero unused rates, throw once, confirm Issues.
1. Copy the project DSN
Open the project → Settings → SDK connection (or Setup). Create a DSN key if none exists. Copy the full string. Do not paste a project UUID from the URL bar into the DSN.
Shape looks like:
https://YOUR_PUBLIC_KEY@errors.example.com/YOUR_PROJECT_ID
2. Swap dsn only
CI fixtures pin @sentry/browser / @sentry/node 7.120.0. Match that if you want the same line the docs exercise. On Sentry JS v8+, also set enableTracing: false when the option exists.
import * as Sentry from "@sentry/node"; // or @sentry/browser@7.120.0
Sentry.init({
dsn: "https://YOUR_PUBLIC_KEY@errors.example.com/YOUR_PROJECT_ID",
environment: "production", // match the Issues filter chip
release: "my-app@1.2.3",
tracesSampleRate: 0,
profilesSampleRate: 0,
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 0,
});
Migrating from Sentry SaaS: keep the same init options. Replace only dsn.
3. Send one test error
Sentry.captureException(new Error("Epure migrate test"));
// short workers: await Sentry.flush(2000) before process exit
4. Confirm ingest, then Issues
Expect ingest HTTP 202 with a body like { "id": "..." }. Refresh Issues. Match the environment filter to Sentry.init (production above, or local in docs examples). You should see an unresolved issue for that message.
Empty feed after a 202? Wait a second, check the env chip, confirm you opened the project that owns the DSN.
What do you keep / drop?
Exceptions and issue triage stay. Replay, APM, profiling, logs, and mobile do not.
| Keep | Drop (out of scope) |
|---|---|
Official Sentry SDKs (@sentry/node, @sentry/browser, …) |
Session replay |
| Envelope / store error path | APM / distributed tracing product |
environment, release, issue grouping |
Continuous profiling |
| Self-hosted data path | Full SaaS suite extras |
Never "100% Sentry compatible." Partial wire for error tracking. If GlitchTip is a better fit for a lighter Django-shaped stack, that is fine. This post is that migrate path.
Deeper SDK init, CORS, and compose details live in the earlier how-to: Point the official Sentry SDK at self-hosted ingest (DSN only). Soft-link only. Do not retell every init step here.
Failure checklist
-
CORS blocks the browser SDK. SPA origin missing from
EPURE_CORS_ORIGINS. Fix origins before blaming the package (CORS post). -
Short worker exits before flush. Node/Go lambda-style processes drop the last event. Call
flush/Flushbefore exit (flush post). - DSN revoked or wrong key. Secret shown once at create. Rotate in Settings → SDK connection if you pasted a dead key.
-
Public URL mismatch.
/healthon localhost is not the host inside the DSN. AlignEPURE_PUBLIC_URL/ proxy with what the SDK posts to. -
Environment chip hides the row. Issues filter is
stagingwhile init saysproduction. Match them. -
Second
Sentry.initstill on SaaS. A leftover init in a shared bootstrap still ships toingest.sentry.io. Grep for everySentry.initand everydsn:.
When the host is healthy and you want the install map in one place: Epure quickstart.
Discussion
What blocked your last DSN swap: CORS, auth, or a missing flush on short workers?
Top comments (0)