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.8ports:# 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:ronetworks:- webnetworks: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
version: '3'services:umami:image: ghcr.io/mikecao/umami:postgresql-latestlabels:- '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/umamiDATABASE_TYPE: postgresqlHASH_SALT: change-this-to-a-random-stringTRACKER_SCRIPT_NAME: protocoldepends_on:- dbrestart: alwaysnetworks:- backend- webdb:image: postgres:12-alpineenvironment:POSTGRES_DB: umamiPOSTGRES_USER: umamiPOSTGRES_PASSWORD: umamivolumes:- ./sql/schema.postgresql.sql:/docker-entrypoint-initdb.d/schema.postgresql.sql:ro- umami-db-data:/var/lib/postgresql/datarestart: alwaysnetworks:- backendvolumes:umami-db-data:networks:backend:external: falseweb: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.
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.
This label specifies that we want TLS activated.
And this label tells Traefik to resolve the certificate with Let's Encrypt.
environment:HASH_SALT: change-this-to-a-random-stringTRACKER_SCRIPT_NAME: protocol
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: falseweb: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.
db runs the database server.
umami needs to join the
db only needs
Our network diagram now looks like this:
traefik:reverse-proxy:networks:- webumami:umami:networks:- web- backenddb:networks:- backend
This ensures that Traefik can route traffic to the Umami frontend and also, that Umami can communicate internally with its database.
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!