Sebastien
← Back

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.

Hetzner VPS Options

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):

Coolify Dashboard

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:

  1. Enable port 25 on your server.
  2. Configure bytemark/smtp to use a different port.
  3. Use a different email provider.

For this project, I chose option 3 and used Resend by:

  1. Removing the mail section from the template, as well as its depends_on relation.
  2. 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.

Resource Selection

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:

Image Settings

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!

Plausible Welcome Screen

I hope this helps. If you have any questions, join the Coolify Discord server and ask away!