Building Muntra

The most reliable tracker you'll never find

Muntra is available on github.com/Tdude/munin

I shipped Muntra to Github this week. It's a small Go service that replaces Umami, the analytics tool I'd been running across my sites. Two evenings to write. Sixteen hours to deploy. This is the story of that gap. And yes, I had AI help writing both the code and this post, but sixteen hours is still sixteen hours.

Why I built it

Umami works, for about two days. Then its memory creeps toward a gig, Docker kills it, and it restarts. The fix everyone recommends is a cron job that restarts the container every 24 hours. That's not a fix. That's an admission.

It's not really Umami's fault. It's a full Next.js app that happens to track pageviews, not a tracker with a dashboard bolted on. So I asked the cheap-engineer question: what's the smallest thing that can ingest events and answer queries, no dashboard, bring-your-own? Turned out to be about a thousand lines of Go.

Visitor identity is a daily hash of IP, user agent, and a salt that rotates every midnight. After 25 hours nobody, me included, can trace a visitor across days. Privacy by construction, not configuration.

Writing it was the fun part. It came together over two evenings, and the whole binary lands at 21MB. Umami's container image alone is fifteen times that size before it's tracked a single visit.

The deploy took the rest of the weekend.

Everything that went wrong

Every single thing that broke was my own doing, not Go's, not Docker's, not nginx's. That's the useful part of this story.

An env var I added in the monorepo silently never reached the server; my own sync script only ever propagated two specific files and quietly dropped the rest. Once that was fixed, the app still didn't pick up the new config, because Docker's build cache had memorized an old, empty .env and kept serving it. That was a few hours chasing a bug that had nothing to do with the running environment and everything to do with a stale layer lying to me.

At one frustrated point I skipped my own deploy script and ran some manual Docker commands straight on the server. That orphaned the container from its database and took the site down for a few genuinely panicked minutes. My own Makefile exists specifically to prevent that mistake. I made it anyway, because in the moment I was absolutely, positively sure I knew better. I didn't.

Then nginx: a provisioning script inserted a new route into the wrong half of the config, The HTTP-redirect block instead of the HTTPS one, so requests quietly 404'd for ninety minutes before I noticed the line was in the file, just on the wrong side of it. Order matters. Any kind of order, just make sure it is the correct one. Right after fixing that, the same script's "helpful" backup copies landed inside the folder nginx actually reads as live config, so a stale backup started fighting the real file for who got to answer requests.

There was also a port nobody computed, a database listening on loopback-only that a blue-green deploy couldn't reach, and one shared-service rebuild that quietly upgraded a dependency and crash-looped a completely unrelated translation service for the better part of an hour. None of it was Muntra's fault. All of it was standing on top of years of my own shortcuts. If I hadn't had AI to do a summary I would have just moved on, but hey, here's a post for your amusement!

The actual lesson

It wasn't eight different bugs. It was the same one, eight times: I'd built an abstraction: a script, a Makefile target, a deploy path. And then, under time pressure, bypassed it to do the "quick" thing by hand. Every time, it cost me hours. The fix isn't a smarter script. It's using the thing I already built, especially when I'm tired and sure I don't need to.

Was it worth it

Hell yeah! Muntra runs at about a twentieth of Umami's memory, with no dashboard of its own, hence bring your own. I fused it into my Svelte dashboard and it has some flair but it's like a tight bikini, it won't fit you. If you want a polished UI out of the box, Umami or Plausible is the better trade. Muntra is for people who'd rather write a thousand lines of Go than run someone else's web app.

It's was running in parallel with Umami on four sites while I confirmed the numbers agree. Once that held for a couple of weeks, Umami got switched off and I got its gigabyte of RAM back. No more nightly restarting makes for better sleep, although it was automated. It's not right...

Small bonus: I never bothered rewriting my own dashboard much, so Muntra just answers to the same field names Umami used. If you're ever making the same switch, it's quieter than it has any right to be.

Code's at github.com/Tdude/muntra. If you've hit the same walls, the cache that lied to you, the wrong nginx block, the "I know better" moment, I'd like to hear about it.

Comments? Hit me!

Log in to like this article, or create an account .

Comments

Log in to post comments. Log in
No comments yet. Be the first to comment.

© 2026 @Tdude. Alla rättigheter förbehållna.