Bitdoze Logo

How to Install Umami Analytics on Docker (2026 Guide)

Learn how to install Umami Analytics with Docker Compose on your VPS. Step-by-step guide to self-host this privacy-focused Google Analytics alternative.

DragosDragos27 min read
How to Install Umami Analytics on Docker (2026 Guide)

Umami is a simple, fast, privacy-focused web analytics tool and a solid Google Analytics alternative. It’s MIT-licensed, has over 37,000 GitHub stars, and collects only the metrics you need without tracking your visitors. If you want to compare options, Plausible and Matomo are similar self-hosted analytics tools. You can also install Plausible Analytics with a similar Docker setup.

With Google Analytics 4 and GDPR enforcement in the EU, self-hosted web analytics has become the default for operators who want full data ownership. Google Analytics scripts are heavy and slow down your site. Umami adds less than 2KB of overhead.

This guide covers how to install Umami Analytics on Docker with Docker Compose, including tracker setup, ad-blocker bypass, upgrades, and backups. Umami v3 shipped in November 2025 with a new UI, heatmaps, session replay, and it’s now PostgreSQL-only (MySQL support was dropped).

Umami v3 (November 2025)

Umami v3 is a major release with a redesigned UI, Segments, Cohorts, Session Replay, Heatmaps, and Web Vitals tracking. MySQL is no longer supported. PostgreSQL is the only database option. The compose files in this guide are updated for v3. See the Umami v3 blog post for full details.

You can find more free open source self-hosted apps at toolhunt.net self hosted section.

Steps to install Umami Analytics with Docker Compose

We’ll deploy Umami on a VPS with Docker Compose, then configure Cloudflare Tunnels for public access with SSL. The video walkthrough below covers the full process.

1. Prerequisites

  • VPS with Docker and Docker Compose v2 installed. A Hetzner CX22 at ~€4/mo is sufficient (Umami + Postgres use ~300MB RAM total). Hostinger is another affordable option.
  • Dockge or any Docker management tool, or just plain docker compose. See the Dockge install guide for a full walkthrough. You can also check other self-hosted server panels.
  • Cloudflare Tunnel configured for your VPS (or any reverse proxy with SSL termination)
  • A domain or subdomain ready for Umami
  • PostgreSQL v12.14+ (we use v15-alpine, which satisfies this)

Don't use 'analytics' as your subdomain

Ad-blockers commonly block requests to analytics.yourdomain.com. Use something like stats, metrics, or track instead. This applies to the subdomain AND the script path.

2. Docker Compose file for Umami

Below are two compose files. The first is a clean setup with no backup, use this if you plan to back up with a cron-based pg_dump (recommended). The second adds a tiredofit/db-backup sidecar for automatic scheduled dumps.

Pin your image version

postgresql-latest now points to Umami v3 (currently v3.2.0 as of June 2026). For reproducible deploys, pin to a specific tag like 3.2.0. Note that v3 tags dropped the v prefix. It’s 3.2.0, not v3.2.0. See Docker Hub tags for all available versions. The image is also available on GHCR (ghcr.io/umami-software/umami:latest) and Docker Hub (umamisoftware/umami:postgresql-latest).

If you need to run multiple PostgreSQL databases on the same host, you can consolidate them into a single Postgres container to save memory.

3. Configure the .env file

Create a .env file in the same directory as your compose file (or add the variables in Dockge’s environment section).

POSTGRES_USER=umami
POSTGRES_PASSWORD=your-secure-password
POSTGRES_DB=umami
APP_SECRET=run-openssl-rand-hex-32-to-generate
DISABLE_TELEMETRY=1

Generate a unique APP_SECRET

Never use the example value. Generate a secure secret with:
openssl rand -hex 32
Paste the output as your APP_SECRET. This is used for session encryption.

4. Deploy Umami

If using Dockge, add a name for your stack and hit deploy. With plain Docker Compose:

docker compose up -d

Verify both containers are healthy:

docker compose ps

You should see both umami and umami-db with status healthy. Check the Umami logs to confirm migrations ran:

docker compose logs -f umami

Healthy output looks like: All migrations have been successfully applied followed by the server listening on port 3000.

Access Umami at http://your-vps-ip:3000. Log in with the default credentials:

  • Username: admin
  • Password: umami

Change the default password now

The default credentials admin / umami are public knowledge. Go to Settings → Profile → Change password immediately, before exposing Umami to the internet.

Quick verification with curl:

curl http://localhost:3000/api/heartbeat
# Should return: OK

5. Configure Cloudflare Tunnels for Umami

Go to Access → Tunnels in the Cloudflare dashboard, choose your tunnel, and add a hostname mapping a domain or subdomain to the Umami service on port 3000.

Cloudflare Tunnel setup for Umami Analytics

Getting 'Unknown' visitor IPs?

If Umami shows “Unknown” for visitor locations behind Cloudflare Tunnels, add CLIENT_IP_HEADER=cf-connecting-ip to the umami service environment block in your compose file. This tells Umami to read the real client IP from Cloudflare’s header. Restart the container after the change.

You can also check Setup CloudPanel as Reverse Proxy with Docker and Dockge to use CloudPanel as a reverse proxy to your Docker containers, or self-host with Docker and Cloudflare Tunnels for another deployment example.

6. Add your first website to Umami

After the tunnel is configured, access Umami at your domain. Log in and go to Settings in the header. You’ll see a button to add a website:

Adding a website in Umami Analytics

After adding a website, click Edit Website (or go to Settings) to find your tracking code. You can paste this into your site’s <head>.

Umami Analytics dashboard overview

Umami tracking code and tracker configuration

Once your website is added, Umami gives you a tracking script. Here’s how to use it and what options are available.

Basic tracking script

The standard embed looks like this:

<script
  defer
  src="https://your-umami-domain.com/script.js"
  data-website-id="your-website-id"
></script>
  • data-website-id (the UUID of the website you added in Umami, visible in the tracking code snippet)
  • data-host-url (set this if your tracker domain differs from the dashboard domain, e.g., you serve the script from a CDN)

Custom events and advanced options

Umami supports both programmatic and declarative event tracking.

Programmatic (call from JavaScript):

// Track a custom event with properties
umami.track("signup", { plan: "pro", source: "header" });

Declarative (add HTML attributes to any element):

<button data-umami-event="click-download" data-umami-file="whitepaper.pdf">
  Download
</button>

Other useful data attributes:

Attribute Purpose
data-domains="example.com,api.example.com" Restrict tracking to specific domains
data-do-not-track="true" Respect the browser’s DNT header
data-auto-track="false" Disable automatic pageview tracking (useful for SPAs that handle their own routing)
data-performance="true" Track Core Web Vitals (new in v3.1.0)
data-auto-pageview="false" Suppress automatic pageview on script load (new in v3.2.0)

If you’re using an Astro site, see Plausible Analytics for Astro with Cloudflare Workers for a related analytics integration approach.

Bypass ad blockers

Ad-blockers target two default paths: /script.js and /api/send. You can rename both with environment variables:

TRACKER_SCRIPT_NAME=custom-tracker
COLLECT_API_ENDPOINT=/api/collect

Add these to the umami service environment in your compose file, then restart. Update your tracking script src to match:

<script
  defer
  src="https://your-domain.com/custom-tracker.js"
  data-website-id="your-website-id"
></script>

Ad-blocker bypass requires compose changes

After adding TRACKER_SCRIPT_NAME and COLLECT_API_ENDPOINT to your compose file, restart the Umami container: docker compose up -d –force-recreate umami. Make sure any reverse proxy rules pass through the new endpoint path.

Full ad-blocker bypass example

Here’s a complete snippet to add to the umami service environment in your compose file:

environment:
  DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@umami-db:5432/${POSTGRES_DB}
  APP_SECRET: ${APP_SECRET}
  DISABLE_TELEMETRY: ${DISABLE_TELEMETRY:-1}
  TRACKER_SCRIPT_NAME: my-stats
  COLLECT_API_ENDPOINT: /api/c
  CLIENT_IP_HEADER: cf-connecting-ip

Then in your site’s HTML:

<script
  defer
  src="https://stats.yourdomain.com/my-stats.js"
  data-website-id="your-website-id"
></script>

If you use a reverse proxy (Nginx, Caddy, Traefik), ensure the custom paths are forwarded to the Umami container on port 3000. No extra routing config is needed if the proxy sends all traffic to Umami.

How to update Umami Analytics on Docker

Standard updates (same major version)

For minor updates within v3.x:

# 1. Back up the database first
docker compose exec -T umami-db pg_dump -U umami umami | gzip > backups/umami-$(date +%F-%H%M).sql.gz

# 2. Pull the latest image
docker compose pull

# 3. Recreate containers
docker compose up -d --force-recreate

# 4. Verify: look for "All migrations have been successfully applied"
docker compose logs -f umami

Upgrading from Umami v2 to v3

Breaking changes in Umami v3

v3 drops MySQL support, has a completely new UI, and runs schema migrations that can take time on large databases. Always back up before upgrading. If you were on MySQL, follow the MySQL to PostgreSQL migration guide first.

Steps:

# 1. Back up
docker compose exec -T umami-db pg_dump -U umami umami > umami-backup-$(date +%F-%H%M).sql

# 2. Pull and recreate
docker compose pull
docker compose up -d --force-recreate umami

# 3. Watch logs for migration output
docker compose logs -f umami

# 4. Run ANALYZE on PostgreSQL (critical after major upgrades)
docker compose exec -T umami-db psql -U umami umami -c "ANALYZE;"

The ANALYZE command updates PostgreSQL’s query planner statistics after schema migrations. The official Umami docs recommend this. Without it, dashboard queries can become slow after a major upgrade.

After upgrading, old Docker images accumulate on disk. You can clean up Docker disk space to reclaim storage.

Database backup and restore

Always back up before upgrading

A failed migration can corrupt data. Always run pg_dump before pulling a new major version.

Verify and troubleshoot your Umami installation

  • docker compose ps: both umami and umami-db show status healthy
  • docker compose logs umami: shows “All migrations have been successfully applied”
  • curl http://localhost:3000/api/heartbeat: returns OK
  • Login works with admin / umami (change password immediately)
  • Embed the tracking script on a test page, open browser DevTools → Network tab, confirm script.js loads (200 OK)
  • Visit the test page, check Umami dashboard for a real-time visit

If you want to monitor your server resources alongside Umami, set up a lightweight monitoring stack.

Container keeps restarting

Check docker compose logs umami. The most common cause is a wrong DATABASE_URL or the database container isn’t healthy yet. Verify your .env variables match what the compose file references. The depends_on condition should wait for the DB health check, but if the DB is slow to start on a low-memory VPS, give it 30 seconds.

'service umami-db not found' error

A typo in depends_on. Make sure it says umami-db (with “m”), not unami-db. This was a bug in earlier versions of this guide’s compose file.

Blank or slow dashboard after upgrade

After a major version upgrade (especially v2 → v3), PostgreSQL’s query planner may have stale statistics. Run:

docker compose exec -T umami-db psql -U umami umami -c "ANALYZE;"
Visitor IPs show as 'Unknown'

When Umami runs behind Cloudflare Tunnels or a reverse proxy, the client IP doesn’t reach it directly. Add to the umami service environment:

CLIENT_IP_HEADER=cf-connecting-ip

Use x-forwarded-for if you’re behind Nginx or Traefik instead of Cloudflare. Restart the container after the change.

Tracking script blocked by ad-blockers

Default paths /script.js and /api/send are on blocklists. Use TRACKER_SCRIPT_NAME and COLLECT_API_ENDPOINT environment variables to rename them. See the bypass ad blockers section above.

Out of memory / OOM kills

Umami uses ~200MB RAM, Postgres ~50-100MB. A 1GB VPS is the practical minimum. Check resource usage with docker stats. If containers are getting OOM-killed, consider adding memory limits in compose:

deploy:
  resources:
    limits:
      memory: 256M

Umami v3: what’s new

Version Date Highlights
v3.0.0 Nov 2025 New UI, Segments, Cohorts, Links, Pixels, Admin page. PostgreSQL-only (MySQL dropped).
v3.1.0 Apr 2026 Boards, Session Replay, Web Vitals, redesigned share page, OR filters, regex, funnels. Requires Node 22.
v3.2.0 Jun 2026 Heatmaps, improved Session Replay, event/session property reporting, data-auto-pageview attribute.

Self-hosting vs Umami Cloud

Self-hosted Umami = unlimited events, full data ownership, free (MIT license). Umami Cloud starts at free (100K events/mo, 1 website) but the Pro plan is $20/mo for 1M events and 20 websites. For a solo operator running a few sites, self-hosting on a ~€4/mo VPS is the clear winner.

PostgreSQL v12.14+ is required for v3. The postgres:15-alpine image used in this guide satisfies this.

Conclusion

You now have Umami Analytics installed on Docker, with a working tracker configuration, ad-blocker bypass options, and a solid upgrade and backup workflow. Umami is free, uses about 300MB of RAM total, and gives you full ownership of your analytics data. No third-party access, no GDPR headaches.

Key things to remember: back up before every major upgrade, pin your image version for reproducibility, and change the default password immediately after first login. If you want to explore other options, you can also install Plausible Analytics with a similar Docker approach, or monitor your server resources to keep an eye on your VPS.

Explore More Self-Hosted Tools