Self-Host Plausible Analytics on a Cheap VPS
I love Plausible. It gets straight to the point (looking at you, GA4). However, if you're used to the free plan of Google Analytics, Plausible comes at a cost. My partner and I use it for our small blog about Saint-Malo, a charming city in France. While we’re doing decent numbers (50K users per month during the high season in 2024), our blog doesn’t generate enough income to justify paying the US$19/month plan.
Since I started using Plausible, I’ve known about their self-hosted edition but never seriously looked into it—until this week.
In this post, I’ll share my experience implementing the Plausible Community Edition on a cheap VPS.
The Drawbacks
Plausible has a full page comparing the paid edition to the self-hosted one, so I won’t repeat all the details here.
For me, my concerns boiled down to:
- Missing features (Plausible CE only gets updates twice a year, and some features are exclusive to the paid edition).
- Managing a server.
The first issue isn’t a big deal for our use case.
The second issue was more worrisome. Although I’ve worked in the web industry for quite some time, I’ve never managed my own infrastructure. Thankfully, solutions exist today to make this much easier.
The Server
I went with the cheapest option I could find and chose a VPS from Hetzner. I selected a CX22 instance with 2 VCPUs and 4 GB RAM (Plausible CE requires at least 2 GB). At the time, it was capped at €3.90 per month. I didn’t mind the server’s location in Germany since most of my projects are based in Europe/France.
Other options besides Hetzner include DigitalOcean.
Coolify
As I mentioned earlier, I lack experience with self-hosting. I’m more familiar with services like Vercel or Netlify.
This is where Coolify shines. In their words, Coolify is "an open-source & self-hostable Heroku / Netlify / Vercel alternative." While this might seem like an unnecessary step—you could simply install Docker on the VPS and follow the Community Edition documentation—I plan to use this VPS for more than just Plausible. Having a Vercel-like platform is worth it for me. Coolify even offers a one-click install for many popular services (Plausible included, but more on that later).
And yes, Coolify offers a self-hosted version! You can find the requirements here. Alternatively, you can use their Cloud version (€5/month for 2 servers). Their documentation is straightforward. Log in to your VPS via SSH and run a single command to deploy Coolify.
Once installed, you should see a dashboard like this (without the Plausible project at this point):
Configuration
Custom Domain
It’s easy to serve your Coolify instance under a custom domain. Go to your Settings page and enter the domain in the Instance’s Domain input field. Then, update your domain’s DNS records by adding a new A record pointing to your VPS’s IP address.
Email / SMTP
If you want to receive emails from your Coolify instance, you need to configure either:
- An SMTP provider and credentials, or
- A Resend API key.
For this, go to Settings > Transactional Emails. Without this setup, no emails will be sent from the instance. I chose Resend because I wanted to try it out, and they offer 100 emails/day for free, which is more than enough.
I won’t cover the Resend setup in this article, but we’ll revisit it later when configuring Plausible emails.
Plausible
Docker Template
Although Plausible is part of Coolify’s service list, they unfortunately don’t offer a "one-click install." Instead, you can use a Docker Compose file provided here.
At the time of writing, it looks like this:
# ignore: true
# documentation: https://plausible.io/docs/self-hosting
# slogan: "Plausible Analytics is a simple, open-source, lightweight (< 1 KB) and privacy-friendly web analytics alternative to Google Analytics."
# tags: analytics, privacy, google, alternative
# port: 8000
services:
plausible:
image: "ghcr.io/plausible/community-edition:v2.1.4"
command: 'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"'
environment:
- SERVICE_FQDN_PLAUSIBLE
- DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@plausible-db:5432/${POSTGRES_DB:-plausible-db}
- CLICKHOUSE_DATABASE_URL=http://plausible-events-db:8123/plausible_events_db
- BASE_URL=${SERVICE_FQDN_PLAUSIBLE}
- SECRET_KEY_BASE=${SERVICE_BASE64_64_PLAUSIBLE}
- TOTP_VAULT_KEY=${SERVICE_REALBASE64_32_TOTP}
depends_on:
plausible-db:
condition: service_healthy
plausible-events-db:
condition: service_healthy
mail:
condition: service_healthy
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://127.0.0.1:8000/api/health",
]
interval: 10s
timeout: 5s
retries: 5
start_period: 45s
mail:
image: bytemark/smtp
platform: linux/amd64
healthcheck:
test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/25' || exit 1"]
interval: 5s
timeout: 10s
retries: 20
plausible-db:
image: "postgres:16-alpine"
volumes:
- plausible-postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=${POSTGRES_DB:-plausible-db}
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 20s
retries: 10
plausible-events-db:
image: "clickhouse/clickhouse-server:24.3.3.102-alpine"
volumes:
- plausible-events-data:/var/lib/clickhouse
- type: bind
source: ./clickhouse/clickhouse-config.xml
target: /etc/clickhouse-server/config.d/logging.xml
read_only: true
content: "<clickhouse><profiles><default><log_queries>0</log_queries><log_query_threads>0</log_query_threads></default></profiles></clickhouse>"
- type: bind
source: ./clickhouse/clickhouse-user-config.xml
target: /etc/clickhouse-server/users.d/logging.xml
read_only: true
content: '<clickhouse><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>'
ulimits:
nofile:
soft: 262144
hard: 262144
healthcheck:
test:
[
"CMD-SHELL",
"wget --no-verbose --tries=1 -O - http://127.0.0.1:8123/ping || exit 1",
]
start_period: 30s
Some notes about this implementation: It is specific to Coolify and slightly different from the official Plausible compose.yml. If you are using Coolify, it is recommended to use their template for the best compatibility.
Plausible and Emails
This was the biggest issue I had to address. Coolify's template leverages the bytemark/smtp
image to handle emails. However, Hetzner blocks port 25 by default, which prevents outgoing emails from being sent. While you can request Hetzner to unblock port 25 after the first month of usage, I decided to use an alternative approach with Resend.
mail:
image: bytemark/smtp
platform: linux/amd64
healthcheck:
test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/25' || exit 1"]
interval: 5s
timeout: 10s
retries: 20
The bytemark/smtp
image uses port 25, which—on Hetzner—is blocked by default. You can request Hetzner to enable it after your first month of usage. However, until then, emails from Plausible will not be sent. If this issue occurs, you’ll see logs like the following:
2024-11-19T10:29:05.621495488Z 10:29:05.621 request_id=GAlX3qwARdsIVMEAAAUB [error] Failed to send e-mail
.
There are multiple ways to resolve this issue:
- Enable port 25 on your server.
- Configure
bytemark/smtp
to use a different port. - Use a different email provider.
For this project, I chose option 3 and used Resend by:
- Removing the
mail
section from the template, as well as itsdepends_on
relation. - Adding the required environment variables to the
services.plausible.environment
section.
The resulting changes are as follows:
services:
plausible:
[xxx]
environment:
- SERVICE_FQDN_PLAUSIBLE
- 'DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@plausible-db:5432/${POSTGRES_DB:-plausible-db}'
- 'CLICKHOUSE_DATABASE_URL=http://plausible-events-db:8123/plausible_events_db'
- 'BASE_URL=${SERVICE_FQDN_PLAUSIBLE}'
- 'SECRET_KEY_BASE=${SERVICE_BASE64_64_PLAUSIBLE}'
- 'TOTP_VAULT_KEY=${SERVICE_REALBASE64_32_TOTP}'
- 'MAILER_ADAPTER=${MAILER_ADAPTER}'
- 'MAILER_EMAIL=${MAILER_EMAIL}'
- 'MAILER_NAME=${MAILER_NAME}'
- 'SMTP_HOST_ADDR=${SMTP_HOST_ADDR}'
- 'SMTP_HOST_PORT=${SMTP_HOST_PORT}'
- 'SMTP_USER_NAME=${SMTP_USER_NAME}'
- 'SMTP_USER_PWD=${SMTP_USER_PWD}'
- 'SMTP_HOST_SSL_ENABLED=${SMTP_HOST_SSL_ENABLED}'
depends_on:
plausible-db:
condition: service_healthy
plausible-events-db:
condition: service_healthy
healthcheck:
[xxx]
You can learn more about how emails work with Plausible here and find details on Resend SMTP usage here.
Docker Usage
Now that we have our Docker template ready, go to Coolify and create a Project. Add a Resource to it.
Select the Docker Compose Empty option and paste the content of your Docker template into the editor. Then, save it.
This will create a new Resource within your project.
Before deploying the resource, you’ll need to configure some settings.
Environment Variables
Since we added new variables in our template, we need to provide their values. Within your resource, navigate to the Environment Variables tab. The new variables should already be listed. Below are the required values for integrating with the Resend SMTP service:
- MAILER_ADAPTER: Bamboo.Mua
- MAILER_EMAIL: [email protected] - This will be used as the sender's "from" email.
- MAILER_Name: Your Name - This will be used as the sender's name.
- SMTP_HOST_ADDR: smtp.resend.com
- SMTP_HOST_PORT: se port 465 or 2465 - Note that Hetzer also block `465`
- SMTP_USER_NAME: resend
- SMTP_USER_PWD: YOUR_API_KEY - A Resend API Key.
- SMTP_HOST_SSL_ENABLED: true
Custom Domain
By default, Coolify assigns a random domain to your resource.
You can point your own domain by going to the Plausible image Settings page:
Here, simply enter your domain in the Domain input field and then hit the Save button.
Once done, add a new A record for your domain in your DNS settings, pointing to the IP address of your VPS. Coolify should handle it properly.
Deployment
At this point, everything should be ready. Click the "Deploy" button on your resource, located in the top right corner. The images will start building. After a few minutes, you should be able to access your domain and see your Plausible Community Edition up and running!
I hope this helps. If you have any questions, join the Coolify Discord server and ask away!