Plausible Analytics Docker Setup: A Privacy-First Google Analytics Alternative
Learn how to self-host Plausible Analytics using Docker Compose. A complete privacy-focused analytics deployment guide with Clickhouse configuration.
ClickHouse Configuration
Plausible Analytics relies on ClickHouse for storing and querying analytics events. ClickHouse is configured to optimize memory usage and performance. Below is the ClickHouse configuration file, /etc/clickhouse-server/config.d/user.xml or custom config file, but typically we override configurations using XML files in clickhouse/clickhouse-config.xml and clickhouse/clickhouse-user-config.xml.
clickhouse-config.xml
<clickhouse>
<logger>
<level>warning</level>
<console>true</console>
</logger>
<!-- Reduce memory consumption for small VPS instances -->
<max_server_memory_usage_to_ram_ratio>0.9</max_server_memory_usage_to_ram_ratio>
<!-- Disable telemetry and sharing -->
<send_crash_reports>
<enabled>false</enabled>
</send_crash_reports>
</clickhouse>
clickhouse-user-config.xml
<clickhouse>
<profiles>
<default>
<!-- Cap memory usage per query (adjust based on VPS RAM, e.g., 2GB here) -->
<max_memory_usage>2000000000</max_memory_usage>
<max_bytes_before_external_group_by>1000000000</max_bytes_before_external_group_by>
<max_bytes_before_external_sort>1000000000</max_bytes_before_external_sort>
</default>
</profiles>
</clickhouse>
Database Settings and Environment Variables
Plausible requires a PostgreSQL database for application metadata (users, sites, goals) and a ClickHouse database for raw pageviews and events. The main configuration is driven via environment variables. Create a .env file in your deployment directory:
# Plausible Base Configuration
BASE_URL=https://plausible.example.com
PORT=8000
SECRET_KEY_BASE=YOUR_SUPER_SECRET_KEY_BASE_64_CHARS
TOTP_VAULT_KEY=YOUR_TOTP_VAULT_KEY_32_CHARS
# Admin User Initialization
ADMIN_USER_EMAIL=admin@example.com
ADMIN_USER_NAME=admin
ADMIN_USER_PWD=YOUR_SECURE_ADMIN_PASSWORD
# ClickHouse DB Connection
CLICKHOUSE_DATABASE_URL=http://clickhouse:8123/plausible_events_db
# PostgreSQL DB Connection
DATABASE_URL=postgres://postgres:YOUR_POSTGRES_PASSWORD@plausible_db:5432/plausible_db
# Disable public registrations after creating admin
DISABLE_REGISTRATION=invite_only
Generate the SECRET_KEY_BASE and TOTP_VAULT_KEY using openssl commands:
openssl rand -base64 48 | tr -d '\n' # For SECRET_KEY_BASE
openssl rand -base64 32 | tr -d '\n' # For TOTP_VAULT_KEY
Docker Compose Configuration
The following docker-compose.yml configures Plausible, its PostgreSQL metadata store, ClickHouse event store, and a Redis instance for caching.
version: '3.8'
services:
plausible_db:
image: postgres:14-alpine
container_name: plausible_db
restart: always
volumes:
- db_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=plausible_db
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=YOUR_POSTGRES_PASSWORD
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
clickhouse:
image: clickhouse/clickhouse-server:24.3-alpine
container_name: plausible_clickhouse
restart: always
volumes:
- clickhouse_data:/var/lib/clickhouse
- ./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
ulimits:
nofile:
soft: 262144
hard: 262144
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
interval: 10s
timeout: 5s
retries: 5
plausible:
image: plausible/analytics:v2.1.4
container_name: plausible_app
restart: always
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
depends_on:
plausible_db:
condition: service_healthy
clickhouse:
condition: service_healthy
ports:
- "127.0.0.1:8000:8000"
env_file:
- .env
volumes:
db_data:
driver: local
clickhouse_data:
driver: local
Reverse Proxy SSL Setup
To secure the Plausible deployment, Nginx acts as a reverse proxy terminated with an SSL/TLS certificate managed by Certbot (Let's Encrypt).
Nginx Server Configuration
Save the configuration below as /etc/nginx/sites-available/plausible.conf and link it to /etc/nginx/sites-enabled/.
server {
listen 80;
listen [::]:80;
server_name plausible.example.com;
# Certbot challenge path
location /.well-known/acme-challenge/ {
root /var/www/html;
}
# Redirect all HTTP requests to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name plausible.example.com;
# SSL Settings
ssl_certificate /etc/letsencrypt/live/plausible.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/plausible.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Proxy Plausible Requests
location / {
proxy_pass http://127.0.0.1:8000;
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;
# Enable buffering for heavy reports
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
}
Let's Encrypt SSL Provisioning
Generate the SSL certificates using Certbot in webroot mode:
sudo certbot certonly --webroot -w /var/www/html -d plausible.example.com --email admin@example.com --agree-tos --no-eff-email
Enable automatic renewal via systemd timers or cron:
echo "0 0 */12 * * root systemctl reload nginx" | sudo tee -a /etc/crontab