Verify Sentry source maps on a self-hosted host (no Symbolicator)
Upload JS/TS .map files with DSN auth, match the SDK release string, demangle stacks in-process. 2 MB cap. No Symbolicator.
On this pageShowHide
On Epure, upload JS/TS .map files under the same release string your official Sentry SDK sends. Demangle runs in-process on the Rust binary with DSN auth and a 2 MB body cap. There is no Symbolicator beside Epure. Still-minified stacks usually mean a missing map or a release-string mismatch.
You already run official @sentry/browser or @sentry/node and want readable stacks on a two-container self-host (Rust binary + PostgreSQL 16). Full Sentry self-host with Symbolicator workers is the undesired alternative for this path. This how-to is the verify path: pin release, DSN-upload .map, throw from the minified bundle, confirm original frames in Issues.
Unique wedge: Epure demangles JS/TS maps in-process on the Rust binary. Symbolicator is not an Epure component. Bias disclosed: Epure first-party docs are the source of truth for Epure behavior; Sentry Symbolicator docs describe a different stack.
Primary how-to: Source maps. Acceptance: Verify. Pin/init: Quickstart. Host shape: Installation. Sibling context: Sentry SDK compatible, Two-container error tracking, Epure vs Sentry.
Still minified after upload
If stacks stay minified after a successful map upload, check that SDK release equals upload {version}, that the Releases UI lists the artifacts, that upload used DSN auth rather than a session cookie, and that the map body is <= 2 MB. Missing maps still ingest and group; frames simply stay minified. Do not assume Symbolicator workers are required on Epure.
Stacks still minified
- Fix (Source maps / Verify)
- `Sentry.init({ release })` must equal `{version}` in the upload URL; check Releases UI for artifacts; DSN auth (not cookie); body <= 2 MB
Session cookie on upload
- Fix (Source maps / Verify)
- Rejected; use DSN `X-Sentry-Auth`
Trying dSYM / ProGuard / NDK
- Fix (Source maps / Verify)
- Out of scope; JS/TS `.map` only
202 but Issues empty
- Fix (Source maps / Verify)
- Wrong project, env filter, or worker flush; wait ~1s; filter `local`; `docker compose logs epure`
401 invalid_dsn
- Fix (Source maps / Verify)
- Copy key from Settings -> SDK connection
403 dsn_revoked
- Fix (Source maps / Verify)
- Create or rotate key
403 project_mismatch
- Fix (Source maps / Verify)
- URL project id must match DSN
Assuming Symbolicator required
- Fix (Source maps / Verify)
- Wrong for Epure; demangle is in-process on the Rust binary
| Dimension | Fix (Source maps / Verify) |
|---|---|
| Stacks still minified | `Sentry.init({ release })` must equal `{version}` in the upload URL; check Releases UI for artifacts; DSN auth (not cookie); body <= 2 MB |
| Session cookie on upload | Rejected; use DSN `X-Sentry-Auth` |
| Trying dSYM / ProGuard / NDK | Out of scope; JS/TS `.map` only |
| 202 but Issues empty | Wrong project, env filter, or worker flush; wait ~1s; filter `local`; `docker compose logs epure` |
| 401 invalid_dsn | Copy key from Settings -> SDK connection |
| 403 dsn_revoked | Create or rotate key |
| 403 project_mismatch | URL project id must match DSN |
| Assuming Symbolicator required | Wrong for Epure; demangle is in-process on the Rust binary |
Match the release string
Tag the SDK with the same release string you use in the upload URL {version} (docs example: my-app@1.0.0). If Sentry.init({ release }) and the upload version differ, demangle is skipped for that event and stacks can stay minified. Build with source maps enabled (Vite, webpack, esbuild, or equivalent), then upload each .map for that release.
When showing install lines, pin the Quickstart versions:
npm install @sentry/browser@7.120.0 or npm install @sentry/node@7.120.0
Example init shape (release string must match the upload {version}): import * as Sentry from "@sentry/browser"; Sentry.init({ dsn: "http://YOUR_PUBLIC_KEY@localhost:8080/YOUR_PROJECT_ID", release: "my-app@1.0.0" });
Upload flow checklist (Source maps):
- Build with source maps enabled.
- Tag the SDK with the same string you upload under (
release: "my-app@1.0.0"in the docs example). - Upload each
.mapvia the release files endpoint. - Ship. New events for that release demangle when a matching map is present.
- Missing maps still ingest and group; frames stay minified.
{version} must not contain .., /, or \.
DSN-auth .map upload
Upload maps with a multipart POST to /api/{project_id}/releases/{version}/files/ using DSN X-Sentry-Auth (sentry_version=7, public key, secret). Session cookies are rejected on this endpoint. A successful upload returns 201 Created with JSON shaped like { "id", "name" }.
Request shape (Source maps): POST /api/{project_id}/releases/{version}/files/ with Content-Type: multipart/form-data and X-Sentry-Auth: Sentry sentry_version=7, sentry_key={public_key}, sentry_secret={secret_key}.
file
- Required
- yes
- Notes (Source maps)
- Artifact bytes (typically `*.map`)
name
- Required
- no
- Notes (Source maps)
- Artifact path; paths like `~/dist/app.min.js.map` accepted
| Dimension | Required | Notes (Source maps) |
|---|---|---|
| file | yes | Artifact bytes (typically `*.map`) |
| name | no | Artifact path; paths like `~/dist/app.min.js.map` accepted |
Live curl from Source maps:
curl -sS -D - -o /tmp/epure-map.json -X POST "http://localhost:8080/api/${PROJECT_ID}/releases/my-app@1.0.0/files/" -H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=${PUBLIC}, sentry_secret=${SECRET}" -F "name=~/dist/app.min.js.map" -F "file=@./dist/app.min.js.map;type=application/json"
Expected (Source maps): HTTP/1.1 201 Created and JSON shaped like { "id": "660e8400-e29b-41d4-a716-446655440001", "name": "~/dist/app.min.js.map" }.
2 MB body cap
Map upload bodies must be <= 2 MB. Stay under the documented Source maps limit. Do not invent a status code for oversize bodies if you only have those two primary docs open; prefer the wording "body <= 2 MB".
This 2 MB cap is a hard limit on the release files upload path. Keep maps under it when you build and upload.
JS/TS only
Symbolication on Epure is JS/TS .map only: no dSYM, ProGuard, or NDK. Browser and Node .map files are in scope; other languages keep raw frames.
Browser / Node JS/TS .map
- Epure demangle
- In scope (in-process on Rust binary)
dSYM (iOS)
- Epure demangle
- Not supported
ProGuard (Android)
- Epure demangle
- Not supported
NDK
- Epure demangle
- Not supported
Non-JS/TS runtimes
- Epure demangle
- Raw frames only
| Dimension | Epure demangle |
|---|---|
| Browser / Node JS/TS .map | In scope (in-process on Rust binary) |
| dSYM (iOS) | Not supported |
| ProGuard (Android) | Not supported |
| NDK | Not supported |
| Non-JS/TS runtimes | Raw frames only |
Verify demangle checklist
After upload, capture an error from the minified bundle with the matching release, then open the event in Issues. Event detail should show original file, function, and line, not only app.min.js:1:.... This is the optional JS/TS sourcemaps confirm step on the Verify docs; baseline ingest still expects 202 Accepted plus an unresolved issue for the exception type.
Optional sourcemaps confirm (Verify):
- Upload minified assets +
.mapfor a release (Source maps). - Capture an error from the minified bundle with matching
release. - Event detail should show original file / function / line, not only
app.min.js:1:.... - Other languages: raw frames only.
Baseline verify throw (Verify): Sentry.captureException(new Error("Epure verify test"));
Store curl (Verify panel; seed defaults) if you need a raw ingest check before the map path:
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"}]}}'
Expected ingest (Verify success table): 202 Accepted + { "id": "<uuid>" }, then an unresolved issue for that exception type. The sourcemaps verify path ends when Issues shows demangled frames for the matching release.
Host reminder: documented Compose is two containers (epure + postgres:16-alpine). See Installation and Quickstart.
Limitations
Epure demangles minified JS/TS stack frames in-process on the Rust binary at ingest when a matching .map is present for that release. You do not run Symbolicator or remote symbol workers next to Epure. Epure self-host for this path is two Compose services (Rust binary + PostgreSQL 16); Redis and Kafka are not defaults.
Negative scope (must-read):
- Epure does not ship or require Symbolicator or remote symbol workers.
- Symbolication is JS/TS
.maponly: no dSYM, ProGuard, or NDK. - Epure is not 100% Sentry compatible.
- Session replay, APM tracing, and profiling are not Epure features.
- Map bodies over 2 MB are out of limits.
- Redis, Kafka, and ClickHouse are not default Epure services for this path.
- This article does not claim demangle latency or percent-of-frames-recovered figures.
Use this guide if
- You already point official Sentry SDKs at a 2-container Epure host and need readable JS/TS stacks.
- You want the no-Symbolicator verify path: matching release -> DSN multipart upload -> Issues demangle check.
- You accept a 2 MB map body cap and JS/TS-only symbolication.
Read something else if
- You still need the DSN-only swap / SDK subset scoreboard: Sentry SDK compatible.
- You are sizing the two-container host: Two-container error tracking.
- Session replay or APM is the daily debug loop: stay on Sentry Cloud (Epure vs Sentry).
Deep links: Source maps | Verify | Quickstart | Installation.