When I first started writing my blog I used getinsights.io for analytics to see how much traffic my site got. Seeing the first visitors finding articles on search engines is very motivating for me.
Using Google Analytics was never an option for me. I don't need half of its features and I didn't want to jump through the GDPR hoops.
What I wasn't happy about when using Insights was the interface and that it still used Google services (Firebase) under the hood. It was also hard to find where the company is seated which is a small red flag for me.
At some point I discovered Umami. Umami is a self-hosted, privacy-focused analytics tool which I wanted to try for some time now.
I'll describe the steps I took to setup Umami on a Digital Ocean droplet, using Traefik as a proxy, providing very easy routing and HTTPS.
I'll use DigitalOcean to create a small droplet. To make my life easier I'll use the Docker preset with the following settings:
After a few minutes our droplet is ready and we can connect to it via SSH.
Here's a list of things I've wanted, so I could later reuse this droplet for other small docker services:
To achieve this I used two docker-compose files in this simple setup:
After logging into the VPS I first created two directories. One for Traefik and one for Umami:
├── traefik└── umami
Next, we'll start by setting up Traefik. For this, I followed this tutorial from DigitalOcean to get started. I'd recommend you first read the things I've done differently before following that tutorial so you can decide for yourself which way you like better.
Instead of using a long docker command I've chosen to use a docker-compose file instead. That way I don't have remember a long-ass command.
Here is the content of that file:
version: '3'services: reverse-proxy: image: traefik:v2.8 ports: # The HTTP port - '80:80' - '443:443' volumes: # So that Traefik can listen to the Docker events - /var/run/docker.sock:/var/run/docker.sock:ro - ./acme.json:/etc/traefik/acme.json - ./traefik.toml:/etc/traefik/traefik.toml:ro - ./traefik_dynamic.toml:/etc/traefik/traefik_dynamic.toml:ro networks: - web networks: web: external: true
Note that we've defined the network web
in here. external: true
means that this is a network managed by Docker itself and not generated while using the up
command provided by docker-compose. To create this network run docker network create web
before starting the service.
You also need to bind this network to your service so that Traefik will be able to communicate in this network.
If you've followed the linked tutorial above you can now run docker-compose up -d
. Using the -d
flag runs your containers detached in the background.
Now onto setting up Umami. Fortunately most of the work is already done because Umami provides a docker-compose file for us. But we will need to make a few changes so Umami can work correctly with Traefik.
Switch into the previously created umami
folder on your VPS and create the following file, naming it docker-compose.yml
:
version: '3'services: umami: image: ghcr.io/mikecao/umami:postgresql-latest labels: - 'traefik.http.routers.umami.rule=Host(`your-domain.com`)' - 'traefik.http.routers.umami.tls=true' - 'traefik.http.routers.umami.tls.certresolver=lets-encrypt' environment: DATABASE_URL: postgresql://umami:umami@db:5432/umami DATABASE_TYPE: postgresql HASH_SALT: change-this-to-a-random-string TRACKER_SCRIPT_NAME: protocol depends_on: - db restart: always networks: - backend - web db: image: postgres:12-alpine environment: POSTGRES_DB: umami POSTGRES_USER: umami POSTGRES_PASSWORD: umami volumes: - ./sql/schema.postgresql.sql:/docker-entrypoint-initdb.d/schema.postgresql.sql:ro - umami-db-data:/var/lib/postgresql/data restart: always networks: - backend volumes: umami-db-data: networks: backend: external: false web: external: true
There are a few changes here in comparison to the original file. I'll explain them in order of appearance and what you'll have to change for it to work correctly for your own setup.
labels: - 'traefik.http.routers.umami.rule=Host(`your-domain.com`)' - 'traefik.http.routers.umami.tls=true' - 'traefik.http.routers.umami.tls.certresolver=lets-encrypt'
These labels will be read by Traefik to configure the router for Umami dynamically when its containers are started. The umami
part in the beginning of the string creates a new router for Umami to use.
traefik.http.routers.umami.rule=Host(your-domain.com)
This label creates a Host
rule. This means, that if the incoming traffic matches the host your-domain.com
it will be forwarded to Umami. Please change this to the domain you plan to use and make sure you've added an A-record which is pointing to the IP address of your VPS.
traefik.http.routers.umami.tls=true
This label specifies that we want TLS activated.
traefik.http.routers.umami.tls.certresolver=lets-encrypt
And this label tells Traefik to resolve the certificate with Let's Encrypt.
environment: HASH_SALT: change-this-to-a-random-string TRACKER_SCRIPT_NAME: protocol
DATABASE_URL
and DATABASE_TYPE
haven't changed because the defaults are fine in this scenario.
HASH_SALT
is used to generate unique values, replace this with a random string.
TRACKER_SCRIPT_NAME
can be used to rename the tracker script. By default it is called umami.js
which unfortunately is blocked by default by some adblockers or the privacy-focused browser Brave. I renamed mine to protocol
which seems to work fine.
This step is very important for everything to work correctly. If you've attempted setting up Traefik before and ran in 502 Bad Gateway or 504 Bad Gateway errors, this should help you.
Lets start with the root level network definitions:
networks: backend: external: false web: external: true
Instead of just one network web
, which we need so Traefik can route the web traffic to the Node server, we also have need a second network.
Normally docker-compose creates a default network for our services to communicate with each other. But since we defined the networks ourselves, this default network is not created. This unfortunately means that the Node server now has no way to communicate with the database service.
That's why we defined backend
as a second network. You can see that we set this one explicitly to not be external. This is the default but when I come back to this configuration in a few weeks or months I don't have to guess the value.
In addition to defining these networks we also have to configure which services use these networks. In our case, we have two services: umami
is the service that is running the Node server and therefore responsible for serving the dashboard and tracking script.
And db
runs the database server.
umami
needs to join the web
and backend
network. db
only needs backend
.
Our network diagram now looks like this:
traefik: reverse-proxy: networks: - web umami: umami: networks: - web - backend db: networks: - backend
This ensures that Traefik can route traffic to the Umami frontend and also, that Umami can communicate internally with its database.
Since the backend
network is an internal network this also ensures that the database service is not reachable publicly which is good for security.
You can now run docker-compose up -d
from the directory the docker-compose file for Traefik is located as well as for Umami. After a few seconds you should be able to visit the Umami dashboard on the domain you've connected.
Once again many thanks to my brother for explaining new things to me ❤️.
I hope this post helped you! If you have any questions, hit me up on X 😊.
You might find these related articles helpful or interesting, make sure to check them out!