Replacing the SaaS bill: how I built my own analytics, uptime, and BI stack with agentic development

analytics observability sqlite nodejs ai javascript
A self-hosted analytics, uptime, and BI stack running on one VPS

Add up what a small portfolio of products is supposed to pay for: Plausible or Fathom for analytics, Pingdom or UptimeRobot for uptime, a Healthchecks plan for cron monitoring, something for dashboards, something for email reports. None of the line items hurt alone. Together they're a few hundred dollars a year forever, for tools whose core loop is a scheduled job writing rows into a database.

I stopped paying and built the whole layer myself. The result is Kleto Pulse, the owner console for my platform: one person plus AI agents, buildless Node services, one SQLite file, and Caddy on a single VPS. This post is the honest version of how that went, including the parts where owning it costs you.

The whole SaaS category is a cron job with a login page

Here's what the replacements actually turned out to be.

Analytics came first. The collector is cookieless and honors DNT and GPC. Each site gets its own salt for hashing visitor identifiers, and the salt rotates and gets deleted every 24 hours, so identifiers can't be linked across days even by me. That's the same privacy posture Plausible and Fathom sell, except I can read every line of the pipeline that enforces it.

Uptime was almost embarrassingly small. A probe hits every enabled site every 5 minutes, writes the check, and opens or closes incident rows. The dashboard computes month-to-date uptime against a 99.9% target, which is the same SLA math the paid monitors charge for. The probe script is shorter than most vendors' pricing pages.

Then the part no SaaS bundle ever gave me in one place: a real BI view. Every site gets a health grade from weighted components (Growth at 25, then Reliability, Performance, and Engagement at 20 each, Loyalty at 15), and a drill-down that walks the AARRR funnel from acquisition to revenue. One scoring rule I'll defend: a missing component counts as zero and the weights never renormalize. If a site has no Lighthouse data, its grade drops until it does. Dashboards that quietly hide missing data are how you end up confident and wrong.

Email digests and cron monitoring round it out. Digests render through one themed shell so every report looks like it came from the same system. Scheduled jobs ping a self-hosted Healthchecks-style dead man's switch with start, success, and fail semantics, so a job that silently stops running becomes visible instead of discovered weeks later.

One SQLite file is the data plane

The architecture is deliberately boring. Every service reads and writes one SQLite database. Analytics events, uptime checks, incidents, Lighthouse runs, likes, subscriptions for the eventual MRR and ARR view: one file, one backup story, one query surface. When I want a number nobody built a chart for, I open the database and write SQL. I made the longer case for this in What is SQLite good for, and this stack is that argument running in production.

The other structural decision is that a registry table of sites is the source of truth. Every scheduled job iterates enabled sites from that table; nothing hardcodes a slug. Adding a site to the platform is an insert, and on the next cycle it's probed, scored, and included in reports without another line of wiring. If a capability ever special-cases one site, I treat that as a bug, because the moment coverage becomes manual, coverage becomes partial.

Buildless is a principle here, not an accident. Deploying means copying files; there's no bundler output to drift from source and no toolchain to rot between deploys. Types still get checked, just without a compile step, which is its own decision worth its own post: Types without the build step.

Agents collapsed the build cost, not the judgment cost

None of this would have been worth building by hand at nights-and-weekends pace. What changed the math is agentic development. I fan work out to AI agents in parallel git worktrees: one wiring the uptime probe, one building the funnel queries, one on the digest shell. Each lands atomic commits with a stated intent, and my job shifts from typing to reviewing the live result and rejecting what's off.

The judgment stayed expensive. Agents cheerfully build the wrong thing fast, so the data model, the scoring weights, and the decision about what counts as an incident all had to come from me, written down before the fan-out. But the grind of implementation, the part that used to make "just pay the $9" the rational call, dropped by an order of magnitude.

Two catches I won't skip

First, my monitor once reported my own deploy as an outage. Recreating the Caddy container leaves a short window where every probe fails, and Kleto Pulse dutifully recorded my sites as down when they weren't. A paid monitor would have handled that grace window for me; I had to notice it, diagnose it, and schedule the fix myself. When you own the tool, you own its false positives.

Second, this is a single node, and durability is entirely my problem. The database replicates continuously through Litestream at a one-second sync interval, and the restore procedure is a written runbook I've actually rehearsed, because a backup you've never restored is a rumor. Before this discipline existed, a silently failing scheduled job once cost me days of analytics in an earlier pipeline on this very blog. Alerting on the absence of a run, not just on errors, is the lesson that whole incident bought.

So here's the sober boundary: if you can't fund the pager with your attention, keep the SaaS. A monitoring stack you built and then abandoned is strictly worse than a bill, because it fails silently while you trust it. The manifesto version of when this trade is worth it is in Stop renting your tools.

What I'd tell a founder in the same spot

Here's what broke: my old analytics cadence failed silently, and rented dashboards never showed me a number I didn't pre-configure. Here's what I did instead: one SQLite data plane, a registry that makes coverage automatic, agents for the implementation grind, and a dead man's switch on every scheduled job. Here's what I wouldn't do again: trust any monitor, bought or built, that only alerts on errors instead of on silence.

FAQ

How long did the replacement actually take?

Weeks of part-time review, not months of building, because agents ran the parallel workstreams. The slow parts were the decisions: schema, scoring weights, incident semantics.

Does self-hosted analytics hold up on privacy?

Cookieless collection, DNT and GPC honored, per-site salts rotated and deleted every 24 hours. That matches what the privacy-first vendors promise, with the difference that I can audit mine.

What would make you go back to a SaaS?

Losing the time to operate it. The stack earns its keep only while someone owns backups, restores, and false positives. The day I can't, renting is the honest choice.

Work with Kleto

I am James Cowan, a product engineer and the founder of Kleto. Kleto is a product engineering agency that ships production software from strategy through handoff. Owned analytics, uptime, and reporting layers like this one are exactly the kind of systems work we build for teams that want their operational data on their own terms. If that matches your stack, contact Kleto and we will scope a sensible first step.

Recommended

Why I Built My Own Analytics Pipeline (And What It Actually Costs) @jameslcowan analytics, javascript
We moved a production API from Node to Bun in an afternoon @jameslcowan bun, javascript
OpenClaw: What It Is and How to Get the Most Out of It @jameslcowan openclaw, javascript

Recommended

Why I Built My Own Analytics Pipeline (And What It Actually Costs) @jameslcowan analytics, javascript
We moved a production API from Node to Bun in an afternoon @jameslcowan bun, javascript
OpenClaw: What It Is and How to Get the Most Out of It @jameslcowan openclaw, javascript

Search by title, tag, description, or the prose itself. Results appear as you type.