Umami is a personal website analytics tool that supports self-deployment. Using their hosted service, you can track up to three sites. I’ve also tried Plausible, but it seemed a bit heavy for my needs - system load increased significantly after using it.

Deployment

  • Create docker-compose.yml and modify according to your situation. Here I integrated Cloudflared’s network for public internet access:

    services:
      umami:
        container_name: umami
        image: ghcr.io/umami-software/umami:postgresql-latest
        environment:
          DATABASE_URL: postgresql://<db_username>:<db_password>@umami-db:5432/umami
          DATABASE_TYPE: postgresql
          APP_SECRET: 
        depends_on:
          umami-db:
            condition: service_healthy
        init: true
        restart: unless-stopped
        networks:
          - umami
          - cloudflared
    
      umami-db:
        container_name: umami-db
        image: postgres:15-alpine
        environment:
          POSTGRES_DB: umami
          POSTGRES_USER: 
          POSTGRES_PASSWORD: 
        volumes:
          - ./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
        networks:
          - umami
    
    networks:
      cloudflared:
        external: true
      umami:
        name: umami
        driver: bridge
    
  • You can generate the secret using OpenSSL:

    openssl rand -base64 32
    
  • Start the containers:

    docker compose up -d
    
  • Add the domain in your reverse proxy. Umami automatically listens on port 3000:

    Subdomain: data
    Domain: your_domain
    Type: HTTP
    URL: umami:3000
    
  • Default username is admin, default password is umami. You can change these directly through the web interface.

  • After adding a website, go to Settings → Tracking Code to get the tracking code. My personal blog uses the Hugo theme, so I paste the tracking code into layouts/partials/head.html.

The final result looks great! Unfortunately, previous analytics data wasn’t recorded. You can preview the dashboard here: Umami

Umami

Embedding into Blog Frontend

This requires a three-tier proxy architecture: the frontend script embeds code in script.js, which calls the umami-api interface, and the API service uses Umami’s token to call Umami and return data to the frontend, which then displays it. I wrote a small service for this: GitHub - Kyxie/umami-api.

  • Create a Team in Umami and move your blog to that Team.

  • Create a new user with limited permissions (e.g., view-only) and add them to the newly created Team. It’s best not to use a high-privilege user, as token leakage could be dangerous.

  • Use Postman or curl to send a request. For username, use the credentials of the new user:

    curl -X POST https://umami_url/api/auth/login \
      -H "Content-Type: application/json" \
      -d '{"username":"username", "password":"password"}'
    
  • You’ll receive a token in the response body.

  • Create a new hostname in Cloudflared. We need umami-api accessible from the public internet:

    Subdomain: umami-api
    domain: your_domain
    type: HTTP
    URL: umami-api:3001
    
  • Create a .env file:

    UMAMI_URL=[your_umami_url]
    UMAMI_TOKEN=[your_token]
    UMAMI_WEBSITE_ID=[your_website_id]
    CORS_ALLOW_ORIGIN=[your_blog_url]
    
    • UMAMI_URL is the Umami URL. Since the API and Umami are in the same container network, you can use the container name (http://umami:3000)
    • UMAMI_TOKEN is the token you just generated
    • UMAMI_WEBSITE_ID is the website’s ID in Umami
    • CORS_ALLOW_ORIGIN is the URL allowed to access the service (e.g., your blog). Multiple URLs can be separated by commas.
  • Create docker-compose.yml:

     umami-api:
        image: kyxie/umami-api:latest
        container_name: umami-api
        env_file: .env
        restart: unless-stopped
        networks:
          - umami
          - cloudflared
    

    Add this section to your umami stack and include it in the cloudflared container network.

  • Start the containers:

    docker compose up -d