Primary keyword: Plausible Docker Compose Caddy ClickHouse production deployment
Secondary keywords: self-hosted analytics, privacy-first analytics, Ubuntu analytics stack, Plausible hardening guide
Introduction: real-world use case
If your team is required to measure website performance and campaign outcomes but must avoid invasive tracking, a self-hosted Plausible stack is one of the most practical options in 2026. It gives marketing, product, and leadership teams trustworthy aggregate metrics without introducing heavyweight cookie consent banners for every pageview workflow. In real organizations, this matters because analytics is not just a dashboard problem: it affects SEO reporting, paid campaign attribution, product release validation, and incident response when traffic drops unexpectedly. The challenge is making analytics reliable, private, and maintainable under production load.
This guide walks through a production-ready deployment of Plausible on Ubuntu using Docker Compose for service orchestration, ClickHouse for event storage, PostgreSQL for app metadata, and Caddy as the edge reverse proxy with automatic TLS. The deployment pattern is designed for small and medium production workloads, with explicit secret handling, health checks, backup strategy, and day-2 operations guidance. You can adapt the same blueprint to staging and multi-environment rollouts while keeping your observability baseline consistent across teams.
By the end, you will have a resilient analytics service with HTTPS, controlled ingress, persistent storage, explicit configuration boundaries, practical verification steps, and operational runbooks you can hand to another engineer without losing context. The instructions are intentionally detailed so this can serve as both an implementation guide and an internal standard operating procedure.
Architecture/flow overview
The stack has four core layers. First, incoming HTTPS requests terminate at Caddy, which handles certificate issuance and renewal automatically. Caddy forwards traffic to the Plausible web service on the internal Compose network. Second, Plausible writes metadata (users, sites, settings, goals) to PostgreSQL. Third, high-volume event ingestion is persisted in ClickHouse, which is optimized for analytical query patterns and significantly improves reporting performance versus storing events in a transactional database alone. Fourth, local persistent volumes hold database data and backups so container replacement does not cause data loss.
Operationally, we separate responsibilities: Caddy handles edge security and public routing, Plausible handles application logic, PostgreSQL handles relational integrity, and ClickHouse handles analytics throughput. This separation reduces blast radius during failures. For example, if Plausible is updated, the data stores remain intact. If Caddy reloads for certificate updates, application and databases continue running. The Compose topology also makes rollback straightforward because every service version is explicitly pinned.
From a security perspective, only Caddy is exposed publicly. PostgreSQL and ClickHouse stay private on Docker networks. Secrets are injected through environment files with restricted permissions, rather than hardcoded in Compose YAML. Backups are executed from host cron with retention policies and regular restore testing. This approach keeps complexity low while still meeting production expectations for availability, recoverability, and privacy-by-default analytics.
Prerequisites
- Ubuntu 22.04/24.04 VPS with at least 2 vCPU, 4 GB RAM, and 60+ GB SSD.
- A DNS A record (for example,
analytics.example.com) pointing to your server. - SSH access with a sudo-capable user.
- Docker Engine + Compose plugin installed.
- Firewall allowing ports 22, 80, and 443.
- An SMTP relay account for Plausible transactional emails (recommended for production).
Before beginning, ensure your server clock is synchronized (NTP) and your package base is updated to reduce security drift during deployment.
Step-by-step deployment
1) Base system preparation
Create a dedicated working directory and lock down permissions so secrets remain readable only to root and the deployment group.
sudo apt update && sudo apt -y upgrade
sudo apt -y install ca-certificates curl gnupg ufw jq
sudo mkdir -p /opt/plausible/{caddy,backups}
sudo chown -R $USER:$USER /opt/plausible
cd /opt/plausible
If copy button does not work in your browser/editor, select the block and copy manually.
2) Install Docker and Compose plugin
If Docker is not already installed, use the official repository path and verify both engine and plugin versions.
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release; echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
docker --version
docker compose version
If copy button does not work in your browser/editor, select the block and copy manually.
3) Generate secrets and environment file
Use strong random values for application secret and database passwords. Keep this file out of version control and permissions at 600.
cat > /opt/plausible/.env <<'EOF'
BASE_DOMAIN=analytics.example.com
[email protected]
SECRET_KEY_BASE=$(openssl rand -hex 64)
POSTGRES_DB=plausible
POSTGRES_USER=plausible
POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d '
')
CLICKHOUSE_PASSWORD=$(openssl rand -base64 32 | tr -d '
')
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=smtp-user
SMTP_PASSWORD=replace-with-smtp-password
[email protected]
EOF
chmod 600 /opt/plausible/.env
If copy button does not work in your browser/editor, select the block and copy manually.
4) Compose file for Plausible, PostgreSQL, and ClickHouse
This compose topology keeps databases internal and uses health checks to reduce startup race conditions.
cat > /opt/plausible/docker-compose.yml <<'EOF'
services:
plausible:
image: ghcr.io/plausible/community-edition:v2.1.5
env_file: .env
depends_on:
postgres:
condition: service_healthy
clickhouse:
condition: service_healthy
environment:
- BASE_URL=https://${BASE_DOMAIN}
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
- CLICKHOUSE_DATABASE_URL=http://default:${CLICKHOUSE_PASSWORD}@clickhouse:8123/plausible_events_db
- MAILER_EMAIL=${SMTP_FROM}
- SMTP_HOST_ADDR=${SMTP_HOST}
- SMTP_HOST_PORT=${SMTP_PORT}
- SMTP_USER_NAME=${SMTP_USER}
- SMTP_USER_PWD=${SMTP_PASSWORD}
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
- DISABLE_REGISTRATION=true
restart: unless-stopped
networks: [internal]
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
networks: [internal]
clickhouse:
image: clickhouse/clickhouse-server:24.3
environment:
- CLICKHOUSE_USER=default
- CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
ulimits:
nofile:
soft: 262144
hard: 262144
volumes:
- clickhouse_data:/var/lib/clickhouse
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8123/ping"]
interval: 10s
timeout: 5s
retries: 12
restart: unless-stopped
networks: [internal]
volumes:
postgres_data:
clickhouse_data:
networks:
internal:
driver: bridge
EOF
If copy button does not work in your browser/editor, select the block and copy manually.
5) Caddy reverse proxy and TLS
Caddy automatically manages certificates and simplifies HTTP-to-HTTPS redirects.
cat > /opt/plausible/caddy/Caddyfile <<'EOF'
${BASE_DOMAIN} {
encode zstd gzip
reverse_proxy plausible:8000
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
EOF
If copy button does not work in your browser/editor, select the block and copy manually.
Add Caddy to the compose stack:
cat >> /opt/plausible/docker-compose.yml <<'EOF'
caddy:
image: caddy:2.8
env_file: .env
ports:
- "80:80"
- "443:443"
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- plausible
restart: unless-stopped
networks: [internal]
volumes:
caddy_data:
caddy_config:
EOF
If copy button does not work in your browser/editor, select the block and copy manually.
6) Start stack and initialize Plausible
Run migrations and create the first admin user through one-off container commands.
cd /opt/plausible
docker compose --env-file .env up -d
docker compose ps
docker compose exec plausible /entrypoint.sh db createdocker compose exec plausible /entrypoint.sh db migratedocker compose exec plausible /entrypoint.sh run
If copy button does not work in your browser/editor, select the block and copy manually.
Open https://analytics.example.com and complete initial login flow. Add your first site domain and verify tracking script placement in your web property.
Configuration/secrets handling
For production, treat .env as sensitive material and avoid storing it in Git repositories or shared chat systems. Restrict permissions to deployment operators only. Rotate SMTP and database credentials quarterly or after personnel changes. If your organization uses a centralized secret manager, template the Compose stack from ephemeral environment injection rather than static plaintext.
At the host level, separate administrative SSH users and require key-based authentication. Keep Docker daemon socket access restricted; members of the docker group effectively have root-equivalent control. Enable unattended security updates or patch windows with explicit maintenance cadence. For compliance-heavy workloads, add encrypted volume backends and backup-at-rest encryption keys managed outside the host.
When handling analytics data, establish a retention policy and regional storage policy that aligns with your privacy commitments. Document exactly what events are collected and avoid adding custom dimensions containing personal data. Plausibleβs privacy-friendly model is strongest when your implementation discipline remains equally strict.
Verification
Validate each layer independently before declaring production ready. Start with container health, then endpoint checks, then application functionality.
cd /opt/plausible
docker compose ps
docker compose logs --tail=80 plausible
docker compose logs --tail=80 clickhouse
docker compose logs --tail=80 postgres
curl -I https://analytics.example.com
curl -s https://analytics.example.com/api/health | jq .
If copy button does not work in your browser/editor, select the block and copy manually.
From the Plausible UI, add a test site and generate a few pageviews. Confirm they appear in the dashboard within expected ingestion latency. Then test SMTP by triggering a password reset email. Finally, run a stop/start cycle (docker compose down && docker compose up -d) to confirm persistence across restarts.
Common issues/fixes
Issue: 502 from Caddy after deploy
Usually caused by Plausible not fully ready or incorrect upstream port. Confirm Plausible is listening on 8000 and health checks are passing. Check container logs for migration errors.
Issue: No analytics data appears
Verify tracking script is installed on the target site and not blocked by CSP or extension policies. Check browser network tab for successful script load and event POST requests.
Issue: ClickHouse connection errors
Most often due to wrong password or malformed CLICKHOUSE_DATABASE_URL. Recheck URL encoding, restart Plausible, and test ClickHouse ping endpoint from inside the network.
Issue: TLS certificate not issued
Ensure DNS points to the server and ports 80/443 are reachable externally. Caddy requires HTTP reachability for ACME challenges unless DNS challenge is configured.
Issue: Slow dashboards under load
Increase server memory, review ClickHouse storage IOPS, and cap noisy custom events. For sustained growth, move databases to dedicated nodes and add monitoring around ingestion latency and query timings.
FAQ
1) Why use ClickHouse instead of only PostgreSQL?
ClickHouse is optimized for columnar analytics and high-volume aggregations, making dashboards faster and more efficient as event volume grows.
2) Can I run this on a small VPS?
Yes for modest traffic, but plan to scale resources as concurrent reporting and event ingestion increase. Start with at least 4 GB RAM for headroom.
3) Is this setup compatible with strict privacy policies?
It can be, provided you avoid personal data in event payloads, define retention boundaries, and publish transparent data handling practices.
4) How do I back up and restore safely?
Back up PostgreSQL and ClickHouse volumes regularly, test restore in staging, and keep retention snapshots plus offsite encrypted copies.
5) Should I expose PostgreSQL or ClickHouse ports publicly?
No. Keep both internal-only on the Docker network. Public exposure increases attack surface and is unnecessary for this architecture.
6) How often should I update containers?
Review image updates at least monthly, test in staging first, then roll to production with rollback tags and post-deploy verification checks.
7) What is the safest way to rotate secrets?
Generate new credentials, update .env, restart dependent services in a controlled window, and verify connectivity before decommissioning old credentials.
Internal links
- https://sysbrix.com/blog/guides-3/deploy-immich-with-docker-compose-caddy-postgresql-on-ubuntu-production-guide-96
- https://sysbrix.com/blog/guides-3/deploy-minio-on-kubernetes-with-helm-production-ready-s3-compatible-object-storage-91
- https://sysbrix.com/blog/guides-3/deploy-uptime-kuma-with-docker-compose-and-caddy-on-ubuntu-production-guide-67
Talk to us
Need help implementing a privacy-first analytics platform with hardened production operations? We can help with architecture, secure rollout, migration planning, and runbook design.