How to self-host Plausible Analytics on Kubernetes

Have you ever felt like Google Analytics has turned into the control panel of an alien spaceship that no mere mortal can operate? And that neither you nor your client really need all those features that come bundled with a 50 KB JS script? Well, that's what happened to me, and that's why a few days ago I went looking around the web analytics market to see what other options were out there.
My criteria were:
- simple traffic monitoring (who, from where, with what agent/device)
- low price
- lightweight
- open source if possible
- GDPR and CCPA compliance
If you'd gone down the same road as me, you'd have found there are plenty of options: Clicky, HubSpot, Mixpanel, Gauges, StatCounter, and a long etcetera. Going through that fairly long list, I realized most of them failed at least one of my requirements, whether because of price, the complexity of the service and a heavy script, or a dull and outdated UI/UX. Most of them, but not Plausible Analytics.
So the decision was made.
Plausible Analytics
Plausible Analytics is a web analytics service that offers simple metrics, a tiny script, a pleasant interface, transparency, and a lot of flexibility since the product is open source. It has strong arguments that convinced me almost instantly: it doesn't use cookies (which makes it fully compliant with the various cookie laws and privacy regulations), it's based in the EU and runs on renewable energy, and, above all, it lets you skip the cloud and self-host it without paying literally anything (although I'd recommend, at the very least, becoming a GitHub sponsor) if, like me, you already have some infrastructure set up.
A naive approach
So I went with self-hosting, with the idea of dropping a Plausible instance into the k8s cluster managed with Rancher. However, even though the documentation section explains how to get everything up and running on a server with Docker, it says nothing about the k8s case.
The first thing I did was follow the naive approach, with a single deployment based on the suggested plausible/analytics:latest image:
- created a new
plausiblenamespace - filled in the configuration with the required environment variables from plausible-conf.env
- set up persistent storage with a bind mount to the previously created
/data/user/plausibledirectory
No luck. The instance couldn't connect to the Postgres and Clickhouse databases that I assumed would already be there. That meant the Plausible image doesn't include everything: it only packages the product's core functionality, and the actual installation requires additional steps to set up the databases. Fine, nothing serious.
Next, I explored the repository. There I found two interesting things:
docker-compose.yml- the
kubernetesfolder
The compose file made it clear that the final setup included a Postgres database (plausible-db), a Clickhouse database (plausible-events-db), and Plausible Analytics itself (plausible), all deployed together. Here's a very basic diagram of the Plausible architecture I drew to better understand it:
You can find more information about the tech stack and how it works in Plausible's great development notes on their blog. I found Technology choices and the Building Plausible recaps from June 2019 and April 2020 especially interesting.
The kubernetes folder contained an unofficial extension of the documentation on how to run it locally with k8s, with a bunch of kubectl commands and all the useful yaml configurations for this case. Great.
An informed approach
After getting familiar with all the yaml files I mentioned and deleting the initial deployment, I was ready to get to work.
Setting up Postgres
I started with the Postgres database deployment. The setup was straightforward, since there was already a plausible-db.yaml. This is everything I did:
-
create a new deployment based on the
postgres:latestimage -
set the container port to
5432 -
set up storage with a bind mount at the path
/data/user/plausible/postgresqland mount point/var/lib/postgresql/data -
add the required environment variables
env:- name: POSTGRES_DBvalue: plausible- name: PGDATAvalue: /var/lib/postgresql/data/pgdata- name: POSTGRES_USERvalue: user- name: POSTGRES_PASSWORDvalue: password -
set up a
ClusterIPtype service discovery, from port5432to5432with TCP protocol, following the configuration
And that was basically it; I left the rest of the settings at their defaults. The Postgres instance started up without any issues.
Setting up Clickhouse
Next up was the Clickhouse database, whose setup is very similar to the previous one, so I won't bore you by repeating it. Here's the plausible-events-db.yaml, which says it all.
The only notable difference is the addition of a ConfigMap:
apiVersion: v1kind: ConfigMapmetadata: name: plausible-events-db-configdata: clickhouse-config.xml: | <yandex> <logger> <level>warning</level> <console>true</console> </logger> {/* Stop all the unnecessary logging */} <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"/> </yandex> clickhouse-user-config.xml: | <yandex> <profiles> <default> <log_queries>0</log_queries> <log_query_threads>0</log_query_threads> </default> </profiles> </yandex>
This is a small Clickhouse optimization. If you're curious what it's for, take a look at this commit
And then it gets added alongside the bind-mount volumes, like this:
volumeMounts: - name: data mountPath: /var/lib/clickhouse - name: config mountPath: /etc/clickhouse-server/config.d/logging.xml subPath: clickhouse-config.xml readOnly: true - name: config mountPath: /etc/clickhouse-server/users.d/logging.xml" subPath: clickhouse-user-config.xml readOnly: true
Add another service discovery and that's it: you now have a Clickhouse database instance.
Setting up Plausible
The last big task was setting up Plausible itself. Same story: I took the plausible.yaml and repeated the previous steps. That said, with two notes:
- There need to be two containers, one of them an
inittype, which runs before the main container to prepare the databases and trigger the migration with the following command:
initContainers: - name: plausible-init image: plausible/analytics:latest command: - '/bin/sh' - '-c' args: - sleep 30 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin
Then the regular container runs.
- Make sure to fill in the required variables correctly. Check the configuration page to learn more about them.
// postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@$(PLAUSIBLE_DB_SERVICE_HOST):$(PLAUSIBLE_DB_SERVICE_PORT)/plausibleDATABASE_URL=postgres://user:password@plausible-db.plausible:5432/plausible// http://$(CLICKHOUSE_USER):$(CLICKHOUSE_PASSWORD)@$(PLAUSIBLE_EVENTS_DB_SERVICE_HOST):$(PLAUSIBLE_EVENTS_DB_SERVICE_PORT)/plausibleCLICKHOUSE_DATABASE_URL=http://user:password@plausible-events-db.plausible:8123/plausibleADMIN_USER_EMAIL=user@mail.comADMIN_USER_NAME=userADMIN_USER_PWD=password// notice the protocolBASE_URL=https://plausible.example.com// generate one by running "openssl rand -base64 64 | tr -d '\n'" in your terminalSECRET_KEY_BASE=
Then you add one more service discovery and everything should work smoothly. There's just one small detail left.
Setting up the reverse proxy
The documentation says:
By default, Plausible runs over unencrypted HTTP on port 8000. We recommend running it with HTTPS behind some kind of reverse proxy.
That seemed reasonable, so in the Cloudflare dashboard I set up a simple A record with the node's IP address, went back to Rancher, and added an Ingress configuration to expose my brand-new Plausible instance to the outside world.
The login screen finally showed up on my trusty secure domain. Mission accomplished.
And that's it!
This has been my experience self-hosting Plausible Analytics on k8s. Even though there are fairly clear hints on how to do it, it's not obvious, and that's exactly why I wrote this article. I hope it's useful to you.
The Plausible team maintains the product as open source and supports the self-hosted version, including instructions for Kubernetes, which makes the installation a lot easier.