The stack
- Plausible app — Phoenix/Elixir web app: tracking endpoint + dashboard + management API.
- PostgreSQL — users, sites, settings, goal definitions.
- ClickHouse — the actual event store (see that tutorial). Aggregations are extremely fast.
docker compose
# docker-compose.yml — based on plausible/community-edition
services:
plausible_db:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: plausible_db
plausible_events_db:
image: clickhouse/clickhouse-server:24-alpine
restart: unless-stopped
volumes:
- event-data:/var/lib/clickhouse
- event-logs:/var/log/clickhouse-server
- ./clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro
ulimits:
nofile: { soft: 262144, hard: 262144 }
plausible:
image: ghcr.io/plausible/community-edition:v3.0
restart: unless-stopped
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"
env_file: .env
volumes:
db-data:
event-data:
event-logs:
.env:
# Generate with: openssl rand -base64 64
SECRET_KEY_BASE=<long-random>
# Generate with: openssl rand -base64 32
TOTP_VAULT_KEY=<long-random>
BASE_URL=https://stats.example.com
DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@plausible_db:5432/plausible_db
CLICKHOUSE_DATABASE_URL=http://plausible_events_db:8123/plausible_events_db
POSTGRES_PASSWORD=<long-random>
# Optional — SMTP for invite emails / password reset
MAILER_EMAIL=noreply@example.com
SMTP_HOST_ADDR=smtp.example.com
SMTP_HOST_PORT=587
SMTP_USER_NAME=noreply@example.com
SMTP_USER_PWD=...
SMTP_HOST_SSL_ENABLED=true
# Disable user registration after creating your admin
DISABLE_REGISTRATION=invite_only
docker compose up -d
docker compose logs -f plausible
First run runs migrations and starts on port 8000. Browse via the reverse proxy to register the first user — that becomes the instance owner.
Reverse proxy
# Caddy
stats.example.com {
reverse_proxy 127.0.0.1:8000
}
Add a site
- Log in, click "Add site." Enter the domain (without https://, e.g.
example.com) and the timezone for reports. - Plausible shows the snippet to install. Paste into the
<head>of every page you want tracked:
<script defer
data-domain="example.com"
src="https://stats.example.com/js/script.js"></script>
That's it. Visits start showing up in the dashboard within seconds.
Custom events and goals
The plain script tracks pageviews only. For custom events (signups, button clicks, form submissions), use the enhanced script and emit events from JavaScript:
<script defer
data-domain="example.com"
src="https://stats.example.com/js/script.tagged-events.js"></script>
<script>
window.plausible = window.plausible || function () {
(window.plausible.q = window.plausible.q || []).push(arguments)
};
</script>
<!-- Then anywhere in the app: -->
<button onclick="plausible('Signup')">Sign up</button>
In the dashboard: Goals → Add Goal → Event Name "Signup". The funnel report shows visitors who eventually triggered each goal.
Bypassing ad blockers (proxy mode)
By 2026, many ad-blockers list plausible.io and similar tracker domains. To improve data quality, serve the script from your own domain via a reverse proxy:
# Caddy — in the app's site block, not Plausible's
example.com {
handle /js/script.js {
reverse_proxy https://stats.example.com {
header_up Host stats.example.com
}
}
handle /api/event {
reverse_proxy https://stats.example.com {
header_up Host stats.example.com
}
}
handle {
root * /var/www/example.com
file_server
}
}
Then change the script tag's URLs to be relative:
<script defer data-domain="example.com" src="/js/script.js"></script>
Now the analytics script is served from the same domain as the page; ad-blockers that match on hostname don't catch it. The endpoint is also same-origin, so the request is private to the user's session with your domain.
Funnels & dimensions
The Community Edition includes:
- Goals — named pageviews or custom events.
- Funnels — a sequence of goals; reports per-step drop-off.
- Custom properties (formerly "custom dimensions") — tag events with extra data (
plausible('Signup', { props: { plan: 'pro' } })), then segment reports by them. - UTM parameters — automatically captured from URL query strings.
- Referrer reports — top referrer domains.
- Engagement — time on page, scroll depth (with the right script variant).
API access
The stats API at /api/v1/stats/ returns the same metrics the dashboard shows, in JSON. Useful for embedding a "live visitors" widget on your site, or for daily / weekly digest emails. API keys are generated under My profile → API keys.
curl -H "Authorization: Bearer <api-key>" \
"https://stats.example.com/api/v1/stats/aggregate?site_id=example.com&period=7d&metrics=visitors,pageviews"
Backups
Three sources:
- Postgres — users, sites, goals, settings.
pg_dumpnightly. - ClickHouse — the events. Heavier but compresses extremely well (10-100x ratio for typical event data). Snapshot via the ClickHouse
BACKUPcommand to S3 / disk. - Both env files (the secret keys + DB passwords).
For ClickHouse specifically:
docker compose exec plausible_events_db clickhouse-client --query \
"BACKUP DATABASE plausible_events_db TO Disk('backups', 'plausible-$(date +%F).zip')"
Plausible vs alternatives
- Umami — similar shape, also self-hosted, MIT-licensed, simpler architecture (Postgres or MySQL only). Easier install; fewer advanced features (no funnels, less polished UX).
- Google Analytics — vastly more features and ecosystem, free, but cookies + consent banner + the well-known privacy trade-offs.
- Posthog — product analytics (heatmaps, session replay, feature flags) more than web analytics. Bigger footprint; the right tool for application telemetry, less so for "how many people visited my marketing site."
For a marketing site, blog, or any web property where you want simple, honest metrics without a cookie banner, Plausible is the cleanest open-source choice in 2026.