You want to know whether anyone is reading your new AI tool, and you'd rather not add a cookie banner or ship your visitors' data to an ad network. Cookie-free analytics gets you page views, referrers and goals with one small script. Umami is the quickest to self-host: a Postgres database, one container and a reverse proxy.
I read the Umami and Plausible docs on 2026-10-09 (some through a summarising fetch, so check exact wording). I validated the Umami compose file below with a YAML parser, docker compose config and caddy validate. I did not bring the stack up on a server in this session, so the deployment is unrun, and I did not measure memory or performance.
Which analytics tool should you pick?
Pick by what you'd have to run and maintain, not by feature lists.
| Option | What you run | License / cost (per sources read 2026-10-09) | Pick it when |
|---|---|---|---|
| Umami, self-hosted | App + PostgreSQL (docs: Postgres 12.14+; Node 18.18+ for source installs) | Open source; your server cost | You want the lightest self-host |
| Plausible Community Edition | Plausible + ClickHouse + Postgres; docs recommend 2 GB RAM or more | AGPLv3; community support only; releases twice a year | You want Plausible's UI and accept the heavier stack |
| Plausible Cloud | Nothing | Starter listed at $9/month (tier for up to 10k pageviews), 30-day trial, no card | You'd rather pay than patch a server |
| Umami Cloud Hobby | Nothing | $0, 100K events/month, 1 website, 6 months retention | Tiny site, zero ops |
| Cloudflare Web Analytics | Nothing | Doc says "available on all plans"; I could not confirm cookie behaviour | You already use Cloudflare, see the free plan guide |
Plausible's self-hosted page lists what Community Edition leaves out: marketing funnels, user journeys, ecommerce revenue goals, SSO and the sites API, plus basic bot filtering based on user agent and referrer spam. If you need those, use the cloud product.
If your launch is small, the free hosted tier is the right answer more often than people admit. Self-host when you want the data on your own machine, you already have a server, or the free limits are too tight.
What does "no cookies" actually mean here?
Umami's FAQ says its tracking code doesn't use cookies and, by default, identifies visitors with an anonymous session identifier. Plausible's data policy says it does not use cookies, browser cache or local storage. It builds a daily visitor ID by hashing a daily salt, your domain, the IP address and the user agent, deletes the salt every 24 hours, and does not store raw IPs or user agents.
The practical consequence: a returning visitor on day two looks like a new visitor. Your "unique visitors" number is a daily count, not a lifetime count. That's the trade you make for skipping consent banners on the analytics itself.
Whether you legally need a banner depends on your jurisdiction and everything else on the page (chat widgets, ad pixels, embedded video). That's a question for a professional, not for a vendor FAQ.
How do you self-host Umami with Docker Compose?
You need a VPS with Docker and Docker Compose, a domain name pointed at it (for example stats.yourdomain.com), and ports 80 and 443 open. For a server to run this on, any provider works. The VPS guides on this site use Hostinger (the KVM 2 plan should be enough for Umami plus a small app, though I did not measure memory use). Some links on this page are affiliate links; they cost you nothing and I only link tools I'd mention anyway. Hetzner, DigitalOcean and others are equally fine, and nothing below depends on the provider. Before you put anything on a new server, run through the VPS hardening checklist.
Umami's repository ships a docker-compose.yml with the app and a postgres:15-alpine database. It publishes port 3000 on all interfaces and hardcodes umami as the database password. Port publishing like that bypasses ufw, as the hardening post explains. So I changed three things: bind to localhost, move secrets into an .env file, and use unless-stopped.
Create compose.yaml:
services:
umami:
image: ghcr.io/umami-software/umami:latest
ports:
- "127.0.0.1:3000:3000"
environment:
DATABASE_URL: postgresql://umami:${POSTGRES_PASSWORD}@db:5432/umami
APP_SECRET: ${APP_SECRET}
depends_on:
db:
condition: service_healthy
init: true
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat"]
interval: 10s
timeout: 5s
retries: 5
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- umami-db-data:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5
volumes:
umami-db-data:
Create the secrets file. The upstream compose file's own comment uses openssl rand -hex 32 for its two-factor key; the same command works for the others:
printf 'POSTGRES_PASSWORD=%s\nAPP_SECRET=%s\n' "$(openssl rand -hex 24)" "$(openssl rand -hex 32)" > .env
chmod 600 .env
(Unrun on a server. The upstream file also sets TWO_FACTOR_ENCRYPTION_KEY, marked "required for two-factor authentication", which I left out. Add it if you want 2FA.)
About the image tag: I used latest because I couldn't confirm the registry's tag naming from the pages I read. The GitHub releases page showed v3.4.0 as the newest release (dated "17 Sep", year not shown). Once you've started it, look up the exact version tag in the registry and pin it, so an update is something you choose. My checker flags latest as a rolling tag, and it's right.
What I ran to validate it
I wrote a small parser-based check (scratch file, not published) that flags ports not bound to loopback, literal secrets, rolling tags, missing restart policies and unknown depends_on targets. On the compose file above:
services: umami, db
volumes: umami-db-data
problems:
- umami: rolling tag ghcr.io/umami-software/umami:latest
docker compose config -q with a sample .env printed nothing and exited 0, which is its way of saying the file parses and all variables resolve. That proves syntax and interpolation. It does not prove the containers start or that the healthcheck passes.
Start it and look at the logs:
docker compose up -d
docker compose ps
docker compose logs --tail 50 umami
How do you put HTTPS in front of it?
Umami listens on 127.0.0.1:3000, so a reverse proxy has to terminate TLS. Caddy fetches certificates for you once the domain points at the server. The Caddyfile is three lines:
stats.example.com {
reverse_proxy 127.0.0.1:3000
}
caddy validate on that file printed "Valid configuration" (I ran it with a stub domain; certificate issuance needs a real DNS record and was not tested). If you prefer nginx or Traefik, see nginx vs Caddy vs Traefik for AI apps.
Put the domain in your DNS first. If you proxy it through Cloudflare, use Full (strict) SSL once Caddy has a certificate, and see the Cloudflare free plan post for the gotchas.
What do you do on first login?
Umami's install page says the first run creates an admin user with the password umami, and instructs you to change it immediately. Do that before anything else, and do it before the dashboard is reachable by strangers. A safe pattern is to open an SSH tunnel for the first login:
ssh -L 3000:127.0.0.1:3000 deploy@your.server.ip
Then browse to http://localhost:3000 on your own machine, sign in, change the password, add your website, and copy the website ID.
How do you add the tracking script?
Add the script to your site's head. Per Umami's tracker configuration page, the script sends data to wherever it was loaded from by default, data-domains restricts reporting to hostnames you list (it matches against window.location.hostname, so www and non-www are different), and data-do-not-track respects the browser's Do Not Track setting. The script URL and file name depend on your Umami version, so copy the tag from the website settings in your dashboard instead of typing one from memory. It will look roughly like this:
<script defer src="https://stats.example.com/SCRIPT-FROM-DASHBOARD" data-website-id="YOUR-ID" data-domains="example.com,www.example.com" data-do-not-track="true"></script>
Setting data-domains stops localhost and staging builds from polluting your numbers. Check it worked: open your site, then see the visit in Umami's realtime view. If nothing appears, look at the browser's network tab for a blocked request. Ad blockers sometimes block known analytics script names, so your numbers will undercount.
What about Plausible Community Edition?
Plausible's README says to clone a tagged release, create an .env with BASE_URL and a SECRET_KEY_BASE of at least 64 bytes, set the HTTP and HTTPS ports, and run docker compose up -d. It needs a CPU that supports SSE 4.2 or NEON, and recommends at least 2 GB of RAM for ClickHouse and Plausible. The self-hosting page is blunt that CE is "community supported only", security fixes "only protect you once you apply the release yourself", and you handle uptime, backups and capacity. Releases are twice a year, so new features reach Cloud first.
I did not run Plausible CE. If your VPS is small or already busy, that RAM recommendation is the deciding factor.
What will go wrong?
- Nothing shows up. Wrong website ID, a domain not listed in
data-domains, or an ad blocker. Test in a clean browser profile. - Containers restart in a loop. Read
docker compose logs db; a changedPOSTGRES_PASSWORDafter the first start won't update the existing volume's password. - Dashboard open to the world with the default login. Change it first, and keep the port on loopback.
- Numbers don't match other tools. Daily hashed visitors, bot filtering and blocked scripts all differ. Pick one tool as the reference and compare trends, not exact counts.
- You forgot backups. Dump Postgres to somewhere off the server, and test a restore once. Provider snapshots help, but I did not check any provider's backup terms for this post, and a database dump you control is better.
Is it worth it?
If your reason is control and cost on a server you already pay for, yes. If your reason is "analytics should be free and effortless", use a hosted free tier and revisit when your traffic or your privacy requirements justify running it yourself. For the broader order of operations around a launch, see the launch checklist for an AI side project.



