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.
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.
-
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
. -
After clicking on
Create
you will be automatically redirected to the dashboard, where you can manage the database. In here, click onConnect
, then onCopy snippet
to copy your database connection string. This will be useful later on, keep it somewhere safe for now.
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:
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 isclickhouse-db-app-something
then this line should beCLICKHOUSE_DATABASE_PING_URL = "https://clickhouse-db-app-something.fly.dev"
CLICKHOUSE_DATABASE_URL
, if your clickhouse db app name isclickhouse-db-app-something
then this line should be like thisCLICKHOUSE_DATABASE_URL = "http://clickhouse-db-app-something.internal:8123/plausible_events_db"
Note thatplausible_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 followingtrue
,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 runningfly launch --no-deploy
in this directory. After running the command, the app name could change, if for example the app name isplausible-foo-123
, then you should setBASE_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:Then put that value as the value of this field like so$ openssl rand -base64 48 GLVzDZW04FzuS1gMcmBRVhwgd4Gu9YmSl/k/TqfTUXti7FLBd7aflXeQDdwCj6Cz
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 hereDATABASE_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 possibleipv6-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 ipv6low-resources.xml
ensures the instance runs at a low resource mode, reducing bills on fly iodocker_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 configureEXTRA_CONFIG_PATH
which is used to patch the network side of plausible to communicate with clickhousedb andDATABASE_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
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
- https://iagocavalcante.com/articles/how-to-deploy-plausible-analytics-at-fly-io
- https://github.com/plausible/community-edition
- https://chatgpt.com
- https://github.com/plausible/analytics/issues/4955
- https://github.com/plausible/analytics/discussions/4904
- https://github.com/plausible/analytics/discussions/454#discussioncomment-184549
- https://github.com/plausible/analytics/blob/master/config/runtime.exs
- https://github.com/plausible/analytics/blob/master/Dockerfile
- https://curl.se/docs/caextract.html
- https://community.fly.io/t/struggling-with-private-networking/8978
- https://fly.io/docs/reference/configuration/
- https://gist.github.com/ruslandoga/c94ce526231fb77930132aaeda3fc3c9
- https://medium.com/@agusmahari/docker-how-to-install-postgresql-using-docker-compose-d646c793f216
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