Module 7 · ~12 min
📊 Know if it's working — analytics with Umami
You'll leave with privacy-friendly analytics on every app — and one honest number that tells you what to build next.
Ship, then measure
You shipped (Module 6). Now the questions that actually matter: is anyone using it? Which app deserves your next evening? Analytics closes the loop — the data picks your next Module 1. You do not need Google Analytics, a cookie banner, or a marketing suite. You need one honest signal: are real humans showing up, and where.
Why Umami (the lazy, own-your-data choice)
- Self-hosted on the same box, behind the same Caddy — no new vendor, no per-event bill, your visitors' data stays yours.
- Cookieless, no personal data → no cookie-consent banner, no GDPR headache. It fits the whole ethos: public data, own your stack.
- One-line snippet per app, one dashboard for the whole fleet.
- Open source and boring — exactly what Module 4 taught you to prefer. It'll still be running in a year.
Real fleet setup. The entire traqqit fleet reports to one Umami instance at stats.<domain> — every app, one dashboard, zero cookie banners. It's two small containers on the same box that hosts everything else.
Stand it up (the same pattern as Module 4)
Umami is just two more containers on your web network: the app (a Next.js server) and its own Postgres. No published ports — the shared Caddy fronts it, like every other app.
# a Postgres just for analytics
docker run -d --name stats-db --restart unless-stopped \
--network web \
-e POSTGRES_DB=umami -e POSTGRES_USER=umami -e POSTGRES_PASSWORD=CHANGE_ME \
-v stats_pgdata:/var/lib/postgresql/data \
postgres:16-alpine
# Umami itself (runs its DB migrations automatically on boot)
docker run -d --name stats --restart unless-stopped \
--network web \
-e DATABASE_TYPE=postgresql \
-e DATABASE_URL=postgresql://umami:CHANGE_ME@stats-db:5432/umami \
-e APP_SECRET=$(openssl rand -hex 16) \
ghcr.io/umami-software/umami:postgresql-latest
Then the three moves you already know from Module 4 — a Caddy block, a reload, a DNS record:
stats.yourdomain.com {
encode gzip
reverse_proxy stats:3000
}
Reload Caddy (the stdin form from Module 4), then add the stats A record — just ask Claude via the Hostinger MCP, exactly like Module 0: "Add an A record for 'stats' → my VPS IP." Browse to https://stats.yourdomain.com, log in with the default admin / umami, and:
⚠️ Change the default admin password immediately — before anything else. The dashboard sits on a public URL; the default login is public knowledge.
Add your app, grab the one-line snippet
In the Umami dashboard: Settings → Websites → Add website with your app's domain. Umami hands you a data-website-id. Drop this single line into your app's <head>:
<script defer src="https://stats.yourdomain.com/script.js"
data-website-id="YOUR-WEBSITE-ID"></script>
For a static file-server app (Module 4), that's a one-line edit to index.html + an scp — served live, no rebuild. One website-id per domain; keep a note of which id belongs to which app.
💡 Framework app (Next.js, etc.)? Use its script include — e.g. next/script with strategy="afterInteractive". The src and data-website-id attributes are identical; only the wrapper differs.
Confirm real visits land
Open your app in a normal browser and watch the Umami dashboard — a visit should appear within seconds. If you later script a headless browser to test and see nothing, that's expected, not a bug:
⚠️ Umami filters bots and headless browsers server-side. A default headless-browser load returns 200 but stores nothing. That's correct — real humans are tracked, bots are excluded. Test from a real browser (or override the user-agent) before you panic that "it's not working."
What the dashboard looks like
Once visits are landing, this is roughly what you'll open each morning — clean, fast, and readable at a glance. Pick a time range (today / 7 days / 30 days) and Umami shows the honest picture: how many real people, the trend versus last period, and where they went.
ILLUSTRATION — A TYPICAL UMAMI VIEW FOR ONE APP (EXAMPLE NUMBERS):
Top pages
Illustration of the Umami UI — your real dashboard shows your own live numbers. No cookies, no personal data, no banner.
What to actually look at
Ignore vanity metrics. The honest signals:
- Are real humans arriving at all? Zero after a launch post means the message or the channel — not the app.
- Which app has a pulse? The one with visitors earns your next evening. Let the data pick your next Module 1.
- Where do they land, where do they leave? A page everyone bounces off is telling you something's unclear.
That's it. One dashboard, a few honest numbers, no cookie banner, no data broker in the middle.
🦥 Don't build your own analytics. It's tempting to log page views into your own database — resist. Umami is the lazy and correct choice: you own the data, there's nothing to maintain, and you skip the entire cookie-consent circus. One snippet, done.
Optional: one number across the whole fleet
Umami lists sites individually and never sums them. Once you have a few apps, a tiny page can call the Umami API (/api/websites, then /api/websites/{id}/stats) and add them up into a single fleet total. Lazy version: don't build it until you have three or more apps and actually want the aggregate. When you do, it's a static page hitting an API — no new infrastructure.
Your turn ✅
- Stand up Umami + its Postgres on your box (two containers, one Caddy block, one DNS record).
- Change the default admin password before doing anything else.
- Add your app as a website; paste the one-line snippet into its
<head>; ship it live. - Open the app in a real browser; confirm the visit shows up in the dashboard.
- After your next launch post, check the honest question: did real humans show up?
🦥 That's the whole playbook. Seven moves: vet the data, build lazy, static-first, deploy on your box, charge only when it earns it, ship in public, and measure what happens. None is hard alone — the value is running the loop end to end until it's automatic. The numbers you just wired up will hand you your next idea. Go open a dataset.
You finished the playbook. 🎉
Now build something, put it online, and watch the first real visitor land. When it happens, I'd genuinely love to see it — alex@datahoundtech.com. And check back: new material lands here, free, forever.