Self-Hosting

Self-Host Privacy-First Analytics (Plausible or Umami) on DigitalOcean

July 26, 202614 min read

Disclosure: Some links in this guide are affiliate links. If you sign up for DigitalOcean through them, ToolsHubKit earns a small commission — at no cost to you.

Google Analytics ships your visitors' data to Google, needs a cookie banner, and buries you in a UI built for enterprises. Self-hosted Plausible or Umami give you a lightweight, cookieless, GDPR-friendly dashboard on infrastructure you control — for the flat cost of a small Droplet. This guide covers both, so you can pick the right one and deploy it with Docker Compose.

Prerequisites:

  • Basic Linux comfort (SSH, sudo)
  • A domain/subdomain for the analytics dashboard (e.g. stats.yourdomain.com)

1. Why Ditch Google Analytics

Google Analytics Plausible / Umami (self-hosted)
Data ownership Google's servers Your Droplet
Cookies / consent banner Required None (cookieless)
Script weight ~45 KB+ < 2 KB
GDPR posture Complex Simple (no PII, no cookies)
Cost "Free" (you're the product) Flat Droplet cost
Dashboard Overwhelming Single clean page

2. Plausible vs Umami — Which to Pick

Plausible Umami
Backend DB PostgreSQL + ClickHouse PostgreSQL or MySQL
RAM footprint Heavier (ClickHouse) Lighter
Min Droplet 2 GB ($12/mo) 1 GB ($6/mo)
Dashboard polish Very refined Clean, simple
Funnels / goals Rich (some paid-tier in cloud, all in CE) Basic goals
Best for Content sites wanting depth Minimalists, many small sites

Rule of thumb: choose Umami if you want the lightest possible footprint on a $6 Droplet and track a handful of sites; choose Plausible if you want richer reports and don't mind the 2 GB Droplet for ClickHouse.

The rest of this guide gives you a Docker Compose file for each — deploy whichever you picked.


3. Provision the Droplet

Create a Droplet at DigitalOcean. Get $200 in free credits for 60 days when you sign up.

Tool Recommended Droplet
Umami Basic $6/mo (1 GB)
Plausible Basic $12/mo (2 GB) — ClickHouse needs headroom

Choose Ubuntu 24.04 LTS x64, add your SSH key, pick the region closest to your visitors.


4. Base Server Setup + Docker

bash
ssh root@YOUR_SERVER_IP

# Admin user
adduser admin && usermod -aG sudo admin
cp -r /root/.ssh /home/admin/ && chown -R admin:admin /home/admin/.ssh

# Harden SSH
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart ssh

# Firewall
ufw allow OpenSSH && ufw allow 80 && ufw allow 443 && ufw enable

Install Docker (re-login as admin first):

bash
sudo apt update && sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker $USER && newgrp docker

5A. Deploy Umami

Create ~/umami/docker-compose.yml:

yaml
version: "3.9"

services:
  umami:
    image: ghcr.io/umami-software/umami:postgresql-latest
    restart: always
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      DATABASE_URL: postgresql://umami:${DB_PASS}@db:5432/umami
      DATABASE_TYPE: postgresql
      APP_SECRET: ${APP_SECRET}
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_DB: umami
      POSTGRES_USER: umami
      POSTGRES_PASSWORD: ${DB_PASS}
    volumes:
      - umami-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U umami"]
      interval: 5s
      timeout: 5s
      retries: 10

volumes:
  umami-db:

Create ~/umami/.env:

env
DB_PASS=change_me_long_random
APP_SECRET=change_me_64_char_random
bash
cd ~/umami
docker compose up -d

Default login is admin / umamichange the password immediately after first login.


5B. Deploy Plausible

Plausible Community Edition ships its own compose setup. Create ~/plausible/docker-compose.yml:

yaml
version: "3.9"

services:
  plausible_db:
    image: postgres:16-alpine
    restart: always
    volumes:
      - plausible-db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

  plausible_events_db:
    image: clickhouse/clickhouse-server:24.3-alpine
    restart: always
    volumes:
      - plausible-events:/var/lib/clickhouse
    ulimits:
      nofile: { soft: 262144, hard: 262144 }

  plausible:
    image: ghcr.io/plausible/community-edition:v2
    restart: always
    command: sh -c "/entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
    depends_on:
      - plausible_db
      - plausible_events_db
    ports:
      - "127.0.0.1:8000:8000"
    environment:
      BASE_URL: https://stats.yourdomain.com
      SECRET_KEY_BASE: ${SECRET_KEY_BASE}
      DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@plausible_db:5432/plausible_db
      CLICKHOUSE_DATABASE_URL: http://plausible_events_db:8123/plausible_events_db

volumes:
  plausible-db:
  plausible-events:

Create ~/plausible/.env:

env
POSTGRES_PASSWORD=change_me_long_random
SECRET_KEY_BASE=change_me_64_char_random

Generate the secret:

bash
openssl rand -base64 48
bash
cd ~/plausible
docker compose up -d
docker compose logs -f plausible   # watch migrations complete

6. Nginx Reverse Proxy + HTTPS

Both tools listen on 127.0.0.1 (localhost only) — Nginx handles public traffic and TLS.

bash
sudo apt install -y nginx certbot python3-certbot-nginx

Create /etc/nginx/sites-available/analytics:

nginx
server {
    listen 80;
    server_name stats.yourdomain.com;

    location / {
        # Umami: proxy to 3000  |  Plausible: proxy to 8000
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
bash
sudo ln -s /etc/nginx/sites-available/analytics /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d stats.yourdomain.com

Point a DNS A record for stats.yourdomain.com at your Droplet IP first.


7. Add the Tracking Snippet

Umami — Settings → Websites → Add website → copy the snippet:

html
<script defer src="https://stats.yourdomain.com/script.js"
        data-website-id="YOUR-WEBSITE-ID"></script>

Plausible — add your site in the dashboard, then:

html
<script defer data-domain="yourdomain.com"
        src="https://stats.yourdomain.com/js/script.js"></script>

Drop the snippet into your site's <head>. In Next.js, add it to app/layout.tsx with the next/script component:

tsx
import Script from 'next/script';

// inside <head> or the root layout body:
<Script
  defer
  data-domain="yourdomain.com"
  src="https://stats.yourdomain.com/js/script.js"
  strategy="afterInteractive"
/>

8. GDPR & Cookieless Notes

Both Plausible and Umami are cookieless and store no personal data — no persistent identifiers, no cross-site tracking, IPs are hashed/discarded. In practice this means:

  • No cookie-consent banner required in most jurisdictions (always confirm with your own legal counsel for your region).
  • Metrics are aggregate: page views, referrers, countries, devices — not individuals.
  • Data lives on your server in your chosen region, simplifying data-residency requirements.

This is the same privacy posture ToolsHubKit takes with its local-first tools: measure what you need, retain nothing you don't.


9. Backups

Back up the Postgres volume nightly to DigitalOcean Spaces:

bash
sudo apt install -y s3cmd
s3cmd --configure   # enter Spaces keys + endpoint

~/backup-analytics.sh:

bash
#!/bin/bash
set -e
STAMP=$(date +%Y%m%d)

# Umami (adjust container name / db as needed)
docker exec umami-db-1 pg_dump -U umami umami | gzip | \
  s3cmd put - s3://your-bucket/analytics/umami-$STAMP.sql.gz

# Keep 14 days
s3cmd ls s3://your-bucket/analytics/ | awk '{print $4}' | sort | head -n -14 | xargs -r s3cmd del
bash
chmod +x ~/backup-analytics.sh
echo "0 3 * * * admin /home/admin/backup-analytics.sh >> /var/log/analytics-backup.log 2>&1" | sudo tee /etc/cron.d/analytics-backup

10. Updates

bash
cd ~/umami   # or ~/plausible
docker compose pull
docker compose up -d
docker image prune -f

Check the project's release notes before crossing a major version — Plausible occasionally needs migration steps.


Related Guides


Resources

Resource Link
DigitalOcean Droplets (+ $200 free credit) Sign up here
Plausible Community Edition https://github.com/plausible/community-edition
Umami https://github.com/umami-software/umami
Umami self-hosting docs https://umami.is/docs
Plausible self-hosting docs https://plausible.io/docs/self-hosting