Skip to main content
  1. Posts/

How to Deploy Plausible on Fly Io

·12 mins· loading · ·
Guides Rant Plausible Fly.io Deployment
Vincent Oei
Author
Vincent Oei
An Aspiring Programmer, A.K.A Rend, Vin-Ren
Table of Contents

Intro
#

When developing websites, developers tend to be interested in the way users use their websites, and such, analytics are their answers, tracking the traffic, custom events, and tons of other data that can be used to improve their website.

If you’ve been in the web development space for some time, then you should have heard of Plausible, an open source analytics tool that’s supposed to be the google analytics of FOSS. Plausible claims to have a really light script ensuring your site runs fast and is also privacy-friendly, meaning all analytics done is anonymous hence no need for GDPR consent nor cookie banners. No wonder it became one of the most favorite analytics alternative to google analytics, especially with its simple and minimalistic UI and configuration flexibility providing ample performance and features for even large scale apps.

In this post, We will be going through how to deploy plausible on Fly.io and also why you shouldn’t do it if you don’t need to.


Deploying Plausible on Fly.io
#

Based on the official Plausible community edition docker compose example, we’ll need to deploy 3 services, one for postgres database, one for clickhouse database, and one for the actual plausible service.

Initial Setup
#

All of the configurations done in this guide has been stored in a github repository, run the following to clone it.

git clone https://github.com/Vin-Ren/plausible-ce-deployments

The configurations for Fly.io is stored in the flyio/ directory, simply cd flyio from the repository root directory.

Important! Please clone the repository before continuing.

Deploying Postgres DB
#

There are multiple options to choose from to do this step, either using fully managed postgres service, neondb, or any of that sort, as long as its an postgres DB and its accessible from the internet, then it will work fine.

Below are provided two ways to deploy a postgres database for this usecase.

NeonDB
#

In this section, for simplicity’s sake, a simple guide to create a postgres database with neondb will be provided.

Visit the NeonDB official website, and create an account/login to your account. After logging in, you can create a new project by clicking on New Project, then a pop-up will appear for you to configure the project.

  1. Make sure to pick the nearest location to you for optimal performance, this DOES matter. For project name and database name, you can fill it with whatever values you see fit, after filling those fields, click on Create.

    NeonDB Configuration Popup Window

  2. After clicking on Create you will be automatically redirected to the dashboard, where you can manage the database. In here, click on Connect, then on Copy snippet to copy your database connection string. This will be useful later on, keep it somewhere safe for now.

    NeonDB Connection String

Fly Io
#

Make sure to have fly cli installed, then run the following command in your command line:

fly pg create

Select all the neccessary configurations as needed, the flow of your configurations should be like below:

Fly Postgres Sample Setup

Make sure to have atleast 1GB of ram, and a sufficient storage space (can be scaled later on).

after which, you can save the connection string somewhere safe to be used later.

Deploying ClickhouseDB
#

Deploying Clickhouse DB is a bit more complicated.

Go into the root directory of the sample repository above, change directory to flyio/clickhouse and modify the configuration file fly.toml.

You can change the app value and mounts.source value as you see fit. In this step, it’s important that you choose a region closest to you.

After which, run

fly launch --no-deploy

Something similiar to below will be shown, select Yes

An existing fly.toml file was found for app plausible-postgres
? Would you like to copy its configuration to the new app?

If you want to tweak other settings as well, select Yes when the next prompt comes up, a browser window will be opened up and you can edit additional configurations from there.

After you’ve finished configuring the app, run the following to deploy it

fly deploy

Deploying Plausible
#

This is the central component of this deployment, and also the most complicated one so far to get working and running.

Go back to the root directory of the repository, then navigate to flyio/plausible. Here you would need to edit two seperate files.

Fly.toml

# fly.toml app configuration file generated for plausible-app on 2025-05-04T14:43:51+07:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'plausible-app'
primary_region = 'sin'

[deploy]
  release_command = "release"

[env]
  CLICKHOUSE_DATABASE_PING_URL = "https://plausible-clickhouse-db.fly.dev"
  CLICKHOUSE_DATABASE_URL = "http://plausible-clickhouse-db.internal:8123/plausible_events_db"
  EXTRA_CONFIG_PATH = "/app/extra_conf.exs"
  DISABLE_REGISTRATION="invite_only"
  DATABASE_CACERTFILE="/app/GLOBAL_CACERT.pem"

[[mounts]]
  source = 'plausible'
  destination = '/var/lib/plausible'

[http_service]
  internal_port = 8000
  force_https = true
  auto_stop_machines = 'stop'
  auto_start_machines = true
  min_machines_running = 0
  processes = ['app']

[[vm]]
  memory = '1gb'
  cpu_kind = 'shared'
  cpus = 1

Fields like app, region, and mounts.source can also be customized, but not required.

In here, you will need to change the environment variables. Below are given examples to make the process more intuitive.

  • CLICKHOUSE_DATABASE_PING_URL, if your clickhouse db app name is clickhouse-db-app-something then this line should be CLICKHOUSE_DATABASE_PING_URL = "https://clickhouse-db-app-something.fly.dev"
  • CLICKHOUSE_DATABASE_URL, if your clickhouse db app name is clickhouse-db-app-something then this line should be like this CLICKHOUSE_DATABASE_URL = "http://clickhouse-db-app-something.internal:8123/plausible_events_db" Note that plausible_events_db can be anything here, because this service will try to intialize the database on each release.
  • DISABLE_REGISTRATION (optional), depending on your usecase, it can be any of the following true, false, invite_only. The other options should be left as is.

.env.example

# All of the following configuration should conform to 
# https://github.com/plausible/community-edition/wiki/Configuration

BASE_URL="https://plausible-app.fly.dev"
SECRET_KEY_BASE="some secret with minimum length of 64 characters"
DATABASE_URL="postgresql://username:password@app-name.fly.dev/database-name?sslmode=verify-full"

# Change this accordingly if you choose to use the mailer service. 
#   Follow the guides included in the same link as above.

# MAILER_ADAPTER="Bamboo.SendGridAdapter"
# MAILER_EMAIL="your email to send emails from"
# SMTP_HOST_ADDR="smtp.sendgrid.net"
# SMTP_HOST_PORT=465
# SMTP_HOST_SSL_ENABLED=true
# SENDGRID_API_KEY="put your sendgrid api key here"

Here you would find the more sensitive configuration fields like secrets and such. Below are also examples of how to fill them in.

  • BASE_URL, this depends on the url which this app will serve from later. But can be infered after running fly launch --no-deploy in this directory. After running the command, the app name could change, if for example the app name is plausible-foo-123, then you should set BASE_URL="https://plausible-foo-123.fly.dev"
  • SECRET_KEY_BASE, this is a secret key that you will need to generate on your own, this key needs to be atleast 64 characters long. You can generate it by using: openssl rand -base64 48, like this:
    $ openssl rand -base64 48
    GLVzDZW04FzuS1gMcmBRVhwgd4Gu9YmSl/k/TqfTUXti7FLBd7aflXeQDdwCj6Cz
    
    Then put that value as the value of this field like so SECRET_KEY_BASE=GLVzDZW04FzuS1gMcmBRVhwgd4Gu9YmSl/k/TqfTUXti7FLBd7aflXeQDdwCj6Cz
  • DATABASE_URL, remember the database connection string we’ve got from setting up postgres? copy that here. This field should look like this after you paste your connection string here DATABASE_URL="postgresql://username:password@app-name.fly.dev/database-name?sslmode=verify-full"

Now that all of the configurations have been done, we are ready to deploy this service. Run:

fly launch --no-deploy

Do a similiar thing here as what you did when you launch the clickhouse db service before. Then to import your secrets, do this:

flyctl secrets import < .env.example

Note: When you need to change the secrets, simply change .env.example’s content and rerun this command to redeploy the app later.

And finally, run

fly deploy

You can access it via the url given by fly io, e.g. plausible-app.fly.dev, and complete your account registrations and configurations there.

If you want to do a more extensive configurations, check out the official wiki of Plausible CE. In addition, to integrate an email service with your self-hosted plausible, you can check out this tutorial: plausible-mua-email and changing the .env.example file as needed.

Congratulations, you now have your own self-hosted plausible! 🥳🎉


Configurations Breakdown
#

ClickhouseDB
#

Fly.toml

# fly.toml app configuration file generated for plausible-clickhouse-db on 2025-05-04T14:38:33+07:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'plausible-clickhouse-db'
primary_region = 'sin'

[build]

[env]
  CLICKHOUSE_SKIP_USER_SETUP="1"

[[mounts]]
  source = 'plausible_clickhouse_data'
  destination = '/var/lib/clickhouse'

[http_service]
  internal_port = 8080
  auto_start_machines = true
  auto_stop_machines = "stop"
  min_machines_running = 0
  processes = ["app"]

[[vm]]
  memory = '1gb'
  cpu_kind = 'shared'
  cpus = 1

This configures the app on fly io. Overall, what this configuration is doing is:

  • Setting an environment variable for clickhouse headless deployment
  • Creates a persistent volume for clickhouse DB data and mounting it to the expected path
  • Setting up auto start and auto stop according to publicly exposed route (by health-check.sh script)
  • Configuring the service VM specs

Dockerfile

FROM clickhouse/clickhouse-server:24.12-alpine

RUN apk add --no-cache python3 curl

# Copy configs directly into image
COPY logs.xml /etc/clickhouse-server/config.d/logs.xml
COPY ipv6-only.xml /etc/clickhouse-server/config.d/ipv6-only.xml
COPY low-resources.xml /etc/clickhouse-server/config.d/low-resources.xml
RUN mv /etc/clickhouse-server/config.d/docker_related_config.xml /trash.xml

# Copy scripts
COPY entrypoint.sh /usr/local/bin/entrypoint-custom.sh
RUN chmod +x /usr/local/bin/entrypoint-custom.sh
COPY health-check.sh /health-check.sh
RUN chmod +x /health-check.sh

ENTRYPOINT ["entrypoint-custom.sh"]

In this dockerfile, we base the image from an official clickhouse server, clickhouse/clickhouse-server:24.12-alpine.

And then we install the required dependencies for a health-check script, will be explained later on.

Following which, we import our configuration files and remove preexisting one which has a conflict with our configuration:

  • logs.xml ensures that the logs put out by clickhouse db is as minimal as possible
  • ipv6-only.xml ensures that the app only tries to connect with the ipv6 network, removing unintuitive errors from users. This is vital due to the way fly io internal network works using only ipv6
  • low-resources.xml ensures the instance runs at a low resource mode, reducing bills on fly io
  • docker_related_config.xml contains some configurations that clashes with our configurations, so we need to remove it.

We also need a custom entrypoint to be able to run this service on demand, to reduce flyio bills, so a new entrypoint is created, appropriately named entrypoint-custom.sh. The entrypoint script simply runs health-check.sh on the background while also running the actual clickhouse entrypoint in the foreground. This enables us to use auto stop and auto start feature built-in from fly io, resulting in decreased idle ontime and reduced costs.

Plausible
#

Fly.toml

# fly.toml app configuration file generated for plausible-app on 2025-05-04T14:43:51+07:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'plausible-app'
primary_region = 'sin'

[deploy]
  release_command = "release"

[env]
  CLICKHOUSE_DATABASE_PING_URL = "https://plausible-clickhouse-db.fly.dev"
  CLICKHOUSE_DATABASE_URL = "http://plausible-clickhouse-db.internal:8123/plausible_events_db"
  EXTRA_CONFIG_PATH = "/app/extra_conf.exs"
  DISABLE_REGISTRATION="invite_only"
  DATABASE_CACERTFILE="/app/GLOBAL_CACERT.pem"

[[mounts]]
  source = 'plausible'
  destination = '/var/lib/plausible'

[http_service]
  internal_port = 8000
  force_https = true
  auto_stop_machines = 'stop'
  auto_start_machines = true
  min_machines_running = 0
  processes = ['app']

[[vm]]
  memory = '1gb'
  cpu_kind = 'shared'
  cpus = 1

We are configuring a few things here:

  • deploy.release_command, which runs only once every time you deploy, ensures the clickhouse database and postgres database is ready to be used, i.e. initialize and migrate them.
  • env, in this section, we are defining some variables that’s used by plausible to configure itself. Aside from user defined configurations, we also configure EXTRA_CONFIG_PATH which is used to patch the network side of plausible to communicate with clickhousedb and DATABASE_CERTFILE to avoid ssl issues regarding connections with postgres db.
  • mounts, simply creates and attach a persistent storage volume to the app.
  • Takes advantage of fly io auto stop and auto start feature via http_service.

Dockerfile

FROM ghcr.io/plausible/community-edition:v3.0.1

ENV TMPDIR=/var/lib/plausible/tmp

COPY entrypoint.sh /entrypoint-custom.sh
RUN chmod +x /entrypoint-custom.sh

USER root
RUN wget https://curl.se/ca/cacert.pem -O /tmp/cacert.pem && \
    mkdir -p /app && mv /tmp/cacert.pem /app/GLOBAL_CACERT.pem
COPY extra_conf.exs /app/extra_conf.exs

ENTRYPOINT ["/entrypoint-custom.sh"]

Takes the official docker image ghcr.io/plausible/community-edition:v3.0.1 as base, then configures a TMPDIR and inserts our custom entrypoint script. We also inject the files required by previous configurations done in fly.toml, i.e. extra_conf.exs and GLOBAL_CACERT.pem in their appropriate directories. After which, we change the entrypoint to our custom one.

entrypoint.sh

#!/bin/sh
ulimit -n 65535

(
  while true; do
    wget $CLICKHOUSE_DATABASE_PING_URL -O /dev/null >/dev/null 2>&1
    sleep 5
  done
) &

if [ "$1" = "release" ]; then
  echo "Preparing plausible for release..."
  echo CREATING DB...
  /entrypoint.sh db createdb
  echo MIGRATING...
  /entrypoint.sh db migrate
else
  echo RUNNING PLAUSIBLE...
  exec /entrypoint.sh run
fi
This custom entrypoint simply sends ping to CLICKHOUSE_DATABASE_PING_URL as long as this service is alive as a keep alive for clickhouse database in the background, while in the foreground, this script prepares for plausible release when pushing a new version and runs it on normal deployment.

extra_conf.exs

import Config

config :plausible, Plausible.IngestRepo,
    hostname: "plausible-clickhouse-db.internal",
    port: 8123,
    database: "events",
    transport_opts: [inet6: true]

This configuration enables your plausible app to communicate with the clickhouse db via fly io internal network, by enabling ipv6 support for IngestRepo within plausible’s internal configurations.


Why You Shouldn’t Use Fly.io to Deploy Plausible in Production
#

This method of deployment is valid and fine, and is very suitable for those exploring new technologies or just messing around with things. However in an environment where a long term deployment is expected, I do not recommend deployment to fly io. Below are my reasons:

  • Really long coldstarts, sometime the connection even timed out
  • Expensive, even with the postgres service out of the consideration, clickhouse db and plausible service alone costs above $0.3 to run daily, accumulating to above $10 monthly. At that price, you should instead opt for some VPS, where you won’t get any cold starts and even full control of your VPS, resulting in a more satisfactory result.

Alternative - You might think, what if I’m a fledging web developer who wants to try using analytics for once? I recommend using Umami, another FOSS analytics app which can be self-hosted and is lighter than Plausible. I’ve hosted it for a week or so, and from what I’ve seen, the cost averages on $0.1/day or about $3/month, which will be waived by fly.io and you don’t get billed anything.

Conclusion
#

In conclusion, Plausible can indeed be deployed to Fly.io, however it’s contra-productive for long lasting deployments, as you can either pay for plausible’s subscription or rent a VPS for a more optimal experience.

Thank you for reading, and may this post be useful to you! ✨

Consider clicking the like button on top of this blog if you think this blog is useful!


References

Making this is such a pain 😫, but i guess thats why its entertaining 😂

P.S. You can share this blog to someone you think might find this useful via these buttons below

Related

My Very First Post
·3 mins· loading
Random Programming Hugo Guide Deployment