Skip to content

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.

$ docker compose up -d # dashboard + collector on :7474

One container. One SQLite file. ~20MB RAM. Backup is cp.

PostHog-styleeventssession replayerror trackingbug reportsone ~14MB binaryone SQLite file~20MB RAM~5KB SDKGPL-3.0

/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.

docker compose up -d

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.

spyglass.config.json
{
  "listen": ":7474",
  "dataDir": "./data",
  "apps": {
    "inventory": { "key": "sg_live_…" }
  },
  "retention": { "replays_days": 21 }
}
your app
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.

< 50MB

RAM under load. If it climbs past that, it's a bug to fix, not a reason to scale out.

1 file

The database is one SQLite file in WAL mode. Backup is cp. No DB process to run.

0 calls

No phone-home, no external services, ever. Everything stays on the operator's machine.