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-authz 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)