Umami is an open-source web analytics platform that replaces Google Analytics with cookieless tracking, custom events, and full data ownership.
Mike Cao built Umami in 2020 after growing frustrated by the slow dashboards and regulatory overhead of Google Analytics. He wanted a self-hosted analytics tool that captured traffic metrics and custom events without cookies, heavy scripts, or third-party tracking. That containerized architecture gives teams custody over visitor telemetry without operational drag.
Google Analytics 4 anchors digital marketing, but its workflow demands configuring complex event tags, navigating nested exploration reports, and maintaining consent banners. Umami eliminates that compliance friction. Its tracking script measures roughly two kilobytes, loads without hurting Core Web Vitals, and records anonymized pageviews, referrers, and events without browser cookies. For small teams and agencies, that removes invasive cookie banners while collecting clean baseline traffic that ad blockers strip from Google Analytics.
The trade-off centers on depth versus clarity. Google Analytics leads in multi-touch attribution, audience remarketing, and integration with Google Ads bidding networks. Teams running high-spend ad campaigns cannot substitute Umami for that ecosystem. However, for product teams, publishers, and agencies tracking launch traffic, referral domains, and conversion goals, Umami replaces hours of report configuration with an immediate single-screen dashboard.
Is Umami free for commercial use? Yes. Umami is open source and can be self-hosted on your own infrastructure for commercial and personal sites without licensing fees.
Umami vs Google Analytics — which should a team pick? Choose Umami for privacy-friendly metrics and data ownership on an intuitive dashboard; choose Google Analytics if your growth depends on Google Ads remarketing and ad attribution.
Can I self-host Umami without Docker? Yes. You can build and run Umami directly from source using Node.js 18 or newer connected to a PostgreSQL database.
Umami is a sensible default for teams that want clean web analytics without privacy liabilities, provided your marketing does not rely on programmatic Google Ads conversion loops. Sustained maintenance with commits throughout 2026 and a permissive MIT license allow organizations to embed the telemetry service into client deliverables or internal infrastructure with complete legal freedom.
We started this exact setup from scratch in a clean Linux machine on Nov 1, 2026 and checked it through Umami's own API: every check passed. Recipes are re-checked regularly and removed if they stop working.
mkdir -p umami && cd umami
curl -fsSL https://open-source.store/tools/umami/compose.yml -o compose.ymlcat > .env <<EOF
APP_SECRET=$(openssl rand -hex 16)
TWO_FACTOR_ENCRYPTION_KEY=$(openssl rand -hex 32)
POSTGRES_PASSWORD=$(openssl rand -hex 12)
EOFAPP_SECRET — Random string that signs login sessions. Generated above; keep it secret.TWO_FACTOR_ENCRYPTION_KEY — 64-character hex key that encrypts two-factor secrets. Generated above; keep it secret.POSTGRES_PASSWORD — Password of the bundled PostgreSQL database. Generated above; keep it secret.docker compose up -dservices:
umami:
image: ghcr.io/umami-software/umami:3.4.1
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://umami:${POSTGRES_PASSWORD}@db:5432/umami
APP_SECRET: ${APP_SECRET}
TWO_FACTOR_ENCRYPTION_KEY: ${TWO_FACTOR_ENCRYPTION_KEY}
depends_on:
db:
condition: service_healthy
init: true
restart: always
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat"]
interval: 5s
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: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5
volumes:
umami-db-data:
Go, gRPC and Protobuf done right, LLM agents in production, and the hard parts of shipping a first product.
Show that Umami is listed as an open-source alternative. Paste into your README:
Ubuntu 24.04 · Apple container, Docker 29.1.3, Compose 2.40.3+ds1-0ubuntu1~24.04.1. Images pulled in 226 s: ghcr.io/umami-software/umami:3.4.1, postgres:15-alpine.
Based on the upstream setup. Official compose. Image pinned to 3.4.1; database password and secrets moved to .env.
umami-1 | 2026-11-01T15:36:02.132213209Z at async n (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:1:11659)
umami-1 | 2026-11-01T15:36:02.132214126Z at async p (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:4598)
umami-1 | 2026-11-01T15:36:02.132214793Z at async v (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:6019)
umami-1 | 2026-11-01T15:36:02.132215626Z at async Module.X [as handler] (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:4:1552)
umami-1 | 2026-11-01T15:36:02.134586501Z Error: Failed to find Server Action. This request might be from an older or newer deployment.
umami-1 | 2026-11-01T15:36:02.134608668Z Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
umami-1 | 2026-11-01T15:36:02.134610126Z at async n (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:1:11659)
umami-1 | 2026-11-01T15:36:02.134611001Z at async p (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:4598)
umami-1 | 2026-11-01T15:36:02.134611709Z at async v (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:6019)
umami-1 | 2026-11-01T15:36:02.134612459Z at async Module.X [as handler] (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:4:1552)
umami-1 | 2026-11-01T15:36:04.214691377Z Error: Failed to find Server Action. This request might be from an older or newer deployment.
umami-1 | 2026-11-01T15:36:04.214722752Z Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
umami-1 | 2026-11-01T15:36:04.214724919Z at async n (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:1:11659)
umami-1 | 2026-11-01T15:36:04.214725669Z at async p (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:4598)
umami-1 | 2026-11-01T15:36:04.214726335Z at async v (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:6019)
umami-1 | 2026-11-01T15:36:04.214727002Z at async Module.X [as handler] (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:4:1552)
umami-1 | 2026-11-01T15:36:04.217375710Z Error: Failed to find Server Action. This request might be from an older or newer deployment.
umami-1 | 2026-11-01T15:36:04.217393044Z Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
umami-1 | 2026-11-01T15:36:04.217394169Z at async n (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:1:11659)
umami-1 | 2026-11-01T15:36:04.217394877Z at async p (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:4598)
umami-1 | 2026-11-01T15:36:04.217395585Z at async v (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:6019)
umami-1 | 2026-11-01T15:36:04.217396252Z at async Module.X [as handler] (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:4:1552)
umami-1 | 2026-11-01T15:36:10.602561672Z Error: aborted
umami-1 | 2026-11-01T15:36:10.602583297Z at ignore-listed frames {
umami-1 | 2026-11-01T15:36:10.602584297Z code: 'ECONNRESET'
umami-1 | 2026-11-01T15:36:10.602584880Z }
umami-1 | 2026-11-01T15:36:10.602585463Z Error: aborted
umami-1 | 2026-11-01T15:36:10.602585963Z at ignore-listed frames {
umami-1 | 2026-11-01T15:36:10.602586463Z code: 'ECONNRESET'
umami-1 | 2026-11-01T15:36:10.602593588Z }
umami-1 | 2026-11-01T15:36:10.602599255Z Error: aborted
umami-1 | 2026-11-01T15:36:10.602600047Z at ignore-listed frames {
umami-1 | 2026-11-01T15:36:10.602600588Z code: 'ECONNRESET'
umami-1 | 2026-11-01T15:36:10.602601088Z }
umami-1 | 2026-11-01T15:36:10.602601547Z Error: aborted
umami-1 | 2026-11-01T15:36:10.602602005Z at ignore-listed frames {
umami-1 | 2026-11-01T15:36:10.602602505Z code: 'ECONNRESET'
umami-1 | 2026-11-01T15:36:10.602602963Z }
umami-1 | 2026-11-01T15:36:11.075154755Z Error: Failed to find Server Action. This request might be from an older or newer deployment.
umami-1 | 2026-11-01T15:36:11.075173088Z Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
umami-1 | 2026-11-01T15:36:11.075173963Z at async n (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:1:11659)
umami-1 | 2026-11-01T15:36:11.075174547Z at async p (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:4598)
umami-1 | 2026-11-01T15:36:11.075175130Z at async v (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:6019)
umami-1 | 2026-11-01T15:36:11.075175838Z at async Module.X [as handler] (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:4:1552)
umami-1 | 2026-11-01T15:36:11.076640547Z Error: Failed to find Server Action. This request might be from an older or newer deployment.
umami-1 | 2026-11-01T15:36:11.076645380Z Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
umami-1 | 2026-11-01T15:36:11.076646297Z at async n (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:1:11659)
umami-1 | 2026-11-01T15:36:11.076646963Z at async p (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:4598)
umami-1 | 2026-11-01T15:36:11.076647505Z at async v (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:6019)
umami-1 | 2026-11-01T15:36:11.076648047Z at async Module.X [as handler] (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:4:1552)
umami-1 | 2026-11-01T15:36:11.101697130Z Error: Failed to find Server Action. This request might be from an older or newer deployment.
umami-1 | 2026-11-01T15:36:11.101709714Z Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
umami-1 | 2026-11-01T15:36:11.101710755Z at async n (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:1:11659)
umami-1 | 2026-11-01T15:36:11.101711464Z at async p (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:4598)
umami-1 | 2026-11-01T15:36:11.101712089Z at async v (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:6019)
umami-1 | 2026-11-01T15:36:11.101721255Z at async Module.X [as handler] (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:4:1552)
umami-1 | 2026-11-01T15:36:11.103443797Z Error: Failed to find Server Action. This request might be from an older or newer deployment.
umami-1 | 2026-11-01T15:36:11.103451797Z Read more: https://nextjs.org/docs/messages/failed-to-find-server-action
umami-1 | 2026-11-01T15:36:11.103452422Z at async n (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:1:11659)
umami-1 | 2026-11-01T15:36:11.103453047Z at async p (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:4598)
umami-1 | 2026-11-01T15:36:11.103453589Z at async v (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:2:6019)
umami-1 | 2026-11-01T15:36:11.103454130Z at async Module.X [as handler] (.next/server/chunks/ssr/0ztg_next_dist_0bigvue31jdx8._.js:4:1552)
db-1 | 2026-11-01T15:34:34.528905920Z The files belonging to this database system will be owned by user "postgres".
db-1 | 2026-11-01T15:34:34.528925837Z This user must also own the server process.
db-1 | 2026-11-01T15:34:34.528926920Z
db-1 | 2026-11-01T15:34:34.528944337Z The database cluster will be initialized with locale "en_US.utf8".
db-1 | 2026-11-01T15:34:34.528945420Z The default database encoding has accordingly been set to "UTF8".
db-1 | 2026-11-01T15:34:34.528946045Z The default text search configuration will be set to "english".
db-1 | 2026-11-01T15:34:34.528946587Z
db-1 | 2026-11-01T15:34:34.528947087Z Data page checksums are disabled.
db-1 | 2026-11-01T15:34:34.528947587Z
db-1 | 2026-11-01T15:34:34.528948045Z fixing permissions on existing directory /var/lib/postgresql/data ... ok
db-1 | 2026-11-01T15:34:34.529856212Z creating subdirectories ... ok
db-1 | 2026-11-01T15:34:34.529879837Z selecting dynamic shared memory implementation ... posix
db-1 | 2026-11-01T15:34:34.536390045Z selecting default max_connections ... 100
db-1 | 2026-11-01T15:34:34.543098129Z selecting default shared_buffers ... 128MB
db-1 | 2026-11-01T15:34:34.555480337Z selecting default time zone ... UTC
db-1 | 2026-11-01T15:34:34.556404754Z creating configuration files ... ok
db-1 | 2026-11-01T15:34:34.601569295Z running bootstrap script ... ok
db-1 | 2026-11-01T15:34:34.658217920Z sh: locale: not found
db-1 | 2026-11-01T15:34:34.658229462Z 2026-11-01 15:34:34.658 UTC [34] WARNING: no usable system locales were found
db-1 | 2026-11-01T15:34:34.788180504Z performing post-bootstrap initialization ... ok
db-1 | 2026-11-01T15:34:34.847492046Z syncing data to disk ... ok
db-1 | 2026-11-01T15:34:34.847508921Z
db-1 | 2026-11-01T15:34:34.847510004Z
db-1 | 2026-11-01T15:34:34.847510671Z Success. You can now start the database server using:
db-1 | 2026-11-01T15:34:34.847511587Z
db-1 | 2026-11-01T15:34:34.847512212Z pg_ctl -D /var/lib/postgresql/data -l logfile start
db-1 | 2026-11-01T15:34:34.847512837Z
db-1 | 2026-11-01T15:34:34.847516462Z initdb: warning: enabling "trust" authentication for local connections
db-1 | 2026-11-01T15:34:34.847517421Z initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
db-1 | 2026-11-01T15:34:34.865359421Z waiting for server to start....2026-11-01 15:34:34.865 UTC [40] LOG: starting PostgreSQL 15.19 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
db-1 | 2026-11-01T15:34:34.865885587Z 2026-11-01 15:34:34.865 UTC [40] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db-1 | 2026-11-01T15:34:34.867466212Z 2026-11-01 15:34:34.867 UTC [43] LOG: database system was shut down at 2026-11-01 15:34:34 UTC
db-1 | 2026-11-01T15:34:34.869025129Z 2026-11-01 15:34:34.869 UTC [40] LOG: database system is ready to accept connections
db-1 | 2026-11-01T15:34:34.958678296Z done
db-1 | 2026-11-01T15:34:34.958690504Z server started
db-1 | 2026-11-01T15:34:34.977637462Z CREATE DATABASE
db-1 | 2026-11-01T15:34:34.978783379Z
db-1 | 2026-11-01T15:34:34.978823462Z
db-1 | 2026-11-01T15:34:34.978831087Z /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
db-1 | 2026-11-01T15:34:34.978832004Z
db-1 | 2026-11-01T15:34:34.979077462Z waiting for server to shut down...2026-11-01 15:34:34.979 UTC [40] LOG: received fast shutdown request
db-1 | 2026-11-01T15:34:34.979747504Z .2026-11-01 15:34:34.979 UTC [40] LOG: aborting any active transactions
db-1 | 2026-11-01T15:34:34.981112546Z 2026-11-01 15:34:34.981 UTC [40] LOG: background worker "logical replication launcher" (PID 46) exited with exit code 1
db-1 | 2026-11-01T15:34:34.981169754Z 2026-11-01 15:34:34.981 UTC [41] LOG: shutting down
db-1 | 2026-11-01T15:34:34.981666671Z 2026-11-01 15:34:34.981 UTC [41] LOG: checkpoint starting: shutdown immediate
db-1 | 2026-11-01T15:34:35.003024171Z 2026-11-01 15:34:35.001 UTC [41] LOG: checkpoint complete: wrote 922 buffers (5.6%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.008 s, sync=0.010 s, total=0.021 s; sync files=301, longest=0.005 s, average=0.001 s; distance=4249 kB, estimate=4249 kB
db-1 | 2026-11-01T15:34:35.003942837Z 2026-11-01 15:34:35.003 UTC [40] LOG: database system is shut down
db-1 | 2026-11-01T15:34:35.079901004Z done
db-1 | 2026-11-01T15:34:35.079915212Z server stopped
db-1 | 2026-11-01T15:34:35.080511962Z
db-1 | 2026-11-01T15:34:35.080519504Z PostgreSQL init process complete; ready for start up.
db-1 | 2026-11-01T15:34:35.080520212Z
db-1 | 2026-11-01T15:34:35.090340754Z 2026-11-01 15:34:35.090 UTC [1] LOG: starting PostgreSQL 15.19 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
db-1 | 2026-11-01T15:34:35.090455629Z 2026-11-01 15:34:35.090 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
db-1 | 2026-11-01T15:34:35.090459879Z 2026-11-01 15:34:35.090 UTC [1] LOG: listening on IPv6 address "::", port 5432
db-1 | 2026-11-01T15:34:35.092787004Z 2026-11-01 15:34:35.092 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db-1 | 2026-11-01T15:34:35.095585629Z 2026-11-01 15:34:35.095 UTC [56] LOG: database system was shut down at 2026-11-01 15:34:35 UTC
db-1 | 2026-11-01T15:34:35.098862837Z 2026-11-01 15:34:35.098 UTC [1] LOG: database system is ready to accept connections