Open source · self-hosted · GPL-3.0 · macOS + Linux
Analytics like PostHog.
Small like a single binary.
PostHog is excellent, but self-hosting it assumes you have cloud-scale infrastructure to run it. Spyglass gives you the same stack (events, session replay, error tracking, and bug reports) sized for internal tools, agencies, and air-gapped boxes that just need analytics on their own machine.
No ClickHouse, no Kafka, no Kubernetes. One ~14MB Go binary, one SQLite file, about 20MB of RAM. Being that small is the whole point.
Spyglass is an open-source, self-hosted, GPL-3.0-licensed alternative to PostHog, Highlight, and OpenReplay for small internal apps. It bundles product analytics, rrweb session replay, error tracking, and bug reporting into one ~14MB Go binary backed by SQLite and a ~5KB browser SDK. It runs on a small local machine: no ClickHouse, Kafka, or Kubernetes, no database server, no phone-home.
One container. One SQLite file. ~20MB RAM. Backup is cp.
/01 · the killer feature
Open any error.
The replay is already there.
For any error or bug report, spyglass assembles the slice [ts−60s, ts+10s]from that user's session, cued and ready, because the data was already on disk. This is Jam / Capture.dev, derived as a query.
Replay clip
Auto-cued to the exact moment it broke, with a synced console pane.
Breadcrumb timeline
Every event in the window: pageviews, clicks, captures.
Network waterfall
The requests around the failure, with status and duration.
Stack / comment on top
The error stack, or the user's bug report and severity.
/02
The whole problem.
Events, replay, errors, and bug reports. Not a slice of it. One install.
01
Events & funnels
Auto pageviews plus explicit capture(). DAU, top events, top pages, and step funnels: SQL GROUP BY, nothing heavier.
02
Session replay
rrweb records the DOM continuously; console logs ride inside the stream. Seek to any moment with a synced console pane.
03
Errors & network
window.onerror, unhandled rejections, console.error, all deduped. fetch/XHR captured with status, duration, and sizes.
04
Bug reports
A floating widget in a closed Shadow DOM, or report() in code. Each report becomes an incident with the replay attached.
/03
Three moving parts.
The SDK ships telemetry. The collector stores it and serves the dashboard. That's it.
01
Run the collector
One static binary (or a Docker container). It writes SQLite + gzipped replay blobs and serves the dashboard.
02
Add the SDK
Initialize @spyglass/sdk once with your app key and the signed-in user. Replay, errors, and network turn on by default.
03
Open the dashboard
Live feed, user timelines, replay, errors, insights, and the incident view, all embedded in the binary at :7474.
/04
Quick start.
Configure the collector once, drop the SDK in, done.
{
"listen": ":7474",
"dataDir": "./data",
"apps": {
"inventory": { "key": "sg_live_…" }
},
"retention": { "replays_days": 21 }
}import { spyglass } from "@spyglass/sdk";
spyglass.init({
endpoint: "http://localhost:7474",
app: "inventory",
key: "sg_live_…",
user: { id: "anand" },
});
spyglass.capture("invoice_created");/05
No servers. On purpose.
Most internal tools have 60 users, not 60 million. SQLite holds hundreds of millions of rows on one machine. So spyglass is a library inside a binary, not a fleet.
RAM under load. If it climbs past that, it's a bug to fix, not a reason to scale out.
The database is one SQLite file in WAL mode. Backup is cp. No DB process to run.
No phone-home, no external services, ever. Everything stays on the operator's machine.