Plausible Analytics

Plausible to self-hosted alternatywa dla Google Analytics. Lekki skrypt trackingowy (~1 kB), brak cookies, zgodność z GDPR bez bannerów. Dashboard czytelny i szybki, bez dziesiątek zakładek i raportów GA4.

Community Edition działa na PostgreSQL (metadane, konfiguracja) + ClickHouse (zdarzenia, pageviews).

Architektura

graph LR
    I[Internet<br/>:443] --> T[Traefik]
    T -->|"/js/* /api/event<br/>(publiczny)"| P[Plausible<br/>:8000]
    T -->|"dashboard<br/>authelia-auth"| P
    P --> PG[PostgreSQL<br/>plausible_db]
    P --> CH[ClickHouse<br/>plausible_events_db]

Ruch do Plausible przechodzi przez Traefik z podziałem na dwa routery (szczegóły w sekcji Traefik poniżej):

  • publiczny (/js/*, /api/event): skrypt trackingowy i endpoint zbierania zdarzeń. Bez Authelia, bo muszą być dostępne dla odwiedzających stronę.
  • chroniony (reszta, w tym dashboard): za middleware authelia-auth z Authelia .

Konfiguracja

docker-compose.yml

services:
  plausible:
    image: ghcr.io/plausible/community-edition
    container_name: plausible
    stop_grace_period: 10s
    restart: unless-stopped
    command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
    depends_on:
      - plausible_db
      - plausible_events_db
    env_file:
      - .env
    networks: [traefik_network, plausible_internal]
    labels:
      com.centurylinklabs.watchtower.enable: true

  plausible_db:
    image: postgres:17-alpine
    container_name: plausible_db
    stop_grace_period: 10s
    restart: unless-stopped
    volumes:
      - ./db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=<haslo>
    networks: [plausible_internal]

  plausible_events_db:
    image: clickhouse/clickhouse-server:26.3-alpine
    container_name: plausible_events_db
    stop_grace_period: 10s
    restart: unless-stopped
    volumes:
      - ./event-data:/var/lib/clickhouse
      - ./event-logs:/var/log/clickhouse-server
      - ./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro
      - ./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro
    networks: [plausible_internal]

networks:
  traefik_network:
    external: true
  plausible_internal:
    driver: bridge

.env

BASE_URL=https://analytics.robertolechowski.com

SECRET_KEY_BASE=<losowy-ciag-min-64-bajty>

HTTP_PORT=8000

DATABASE_URL=postgres://postgres:<haslo>@plausible_db:5432/plausible_db
CLICKHOUSE_DATABASE_URL=http://default:XXXXXXXX@plausible_events_db:8123/plausible_events_db

DISABLE_REGISTRATION=invite_only

Konfiguracja ClickHouse

ClickHouse domyślnie generuje dużo logów wewnętrznych. Dwa pliki XML redukują to do minimum.

/etc/clickhouse-server/config.d/logging.xml

<clickhouse>
    <listen_host>0.0.0.0</listen_host>
    <logger>
        <level>warning</level>
        <console>true</console>
    </logger>

    <query_thread_log remove="remove"/>
    <query_log remove="remove"/>
    <text_log remove="remove"/>
    <trace_log remove="remove"/>
    <metric_log remove="remove"/>
    <asynchronous_metric_log remove="remove"/>
    <session_log remove="remove"/>
    <part_log remove="remove"/>
</clickhouse>

/etc/clickhouse-server/users.d/logging.xml

<clickhouse>
    <profiles>
        <default>
            <log_queries>0</log_queries>
            <log_query_threads>0</log_query_threads>
        </default>
    </profiles>
    <users>
        <default>
            <password>XXXXXXXX</password>
            <networks>
                <ip>::/0</ip>
            </networks>
            <profile>default</profile>
            <access_management>1</access_management>
        </default>
    </users>
</clickhouse>

Router Traefik

Konfiguracja w pliku dynamic/analytics.yaml na hoście z Traefik .

http:
  routers:
    plausible-public:
      rule: "Host(`analytics.robertolechowski.com`) && (PathPrefix(`/js/`) || PathPrefix(`/api/event`))"
      entryPoints: [websecure]
      tls:
        certResolver: letsencrypt
      service: plausible-service
      priority: 100

    plausible:
      rule: "Host(`analytics.robertolechowski.com`)"
      entryPoints: [websecure]
      tls:
        certResolver: letsencrypt
      service: plausible-service
      middlewares: [authelia-auth]

  services:
    plausible-service:
      loadBalancer:
        servers:
          - url: "http://plausible:8000/"

plausible-public (priority 100) przepuszcza /js/* i /api/event bez Authelia (skrypt i endpoint zdarzeń muszą być publiczne), reszta ruchu idzie przez authelia-auth.

Skrypt trackingowy

Na każdej stronie, która ma być śledzona, dodać w <head>:

<script defer data-domain="robertolechowski.com"
        src="https://analytics.robertolechowski.com/js/script.js">
</script>

data-domain filtruje zdarzenia per domena. defer zapewnia, że skrypt nie blokuje renderowania strony.

Struktura katalogów

plausible/
├── docker-compose.yml
├── .env
├── clickhouse/
│   ├── clickhouse-config.xml
│   └── clickhouse-user-config.xml
├── db-data/          # PostgreSQL data (gitignore)
├── event-data/       # ClickHouse data (gitignore)
└── event-logs/       # ClickHouse logs (gitignore)