Self-hosting Matomo with Docker

Matomo is a viable alternative to Google Analytics as an ethical and privacy-oriented web analytics tool. In this post we see how to install it with Docker, alongside MariaDB and a couple of nginx-related containers.

Matomo is a web analytics platform, which allows us to obtain statistics about the visitors and content of our websites.

What distinguishes Matomo (but also other similar platforms) from the big and easy Google Analytics, is the fact that with the former we have total control and ownership of the data.

In this article we see how to self-host Matomo, with a setup consisting of:

We also assume that we already have:

What we want

Suppose we have a web project on mysite.ext. We want to shove Matomo into a subdomain stats.mysite.ext.

To do this we trivially need to:

  • Configure a DNS record of type CNAME that makes the subdomain an alias of the main domain.
  • Install Matomo and expose it to https://stats.mysite.ext.

After creating that record, we move on to the second step.


We create a ~/matomo folder with in it:


version: "2"

    container_name: matomo
    image: matomo
      - 8080:80
      - MATOMO_DATABASE_HOST=matomo_db
      - VIRTUAL_HOST=stats.mysite.ext
      - LETSENCRYPT_HOST=stats.mysite.ext
      - LETSENCRYPT_EMAIL=email@someting.ext
      - ./db.env
      - proxy
      - net
      - matomo_db
    restart: unless-stopped

    container_name: matomo_db
    image: mariadb
    command: --max-allowed-packet=64MB
      - MYSQL_ROOT_PASSWORD=inventa
      - ./db.env
      - net
    restart: unless-stopped

      name: nginx-proxy
    driver: bridge

Notable stuff:

  • We attached matomo to the existing nginx-proxy network, on which our web project also runs.
  • We have defined a new net, in which both matomo and matomo_db are present, but we do not need the latter to be part of nginx-proxy.
  • The environment variables LETSENCRYPT_* are needed for letsencrypt-nginx-proxy-companion.
  • We have defined a dependency (and thus a starting order) relationship between the two containers.



As soon as we are ready docker-compose up -d.

We verify that our nice containers are running with docker ps. If all is well, let’s move on.

We note that at this point we should already have an active, working SSL certificate for our subdomain.

Installing Matomo

After running our stack, if we go to stats.mysite.ext we will be greeted by the Matomo installation screen.

We just go ahead to the DB configuration, which will already be compiled (thanks to the MATOMO_* environment variables), except for the DB password, which must be the value of MYSQL_PASSWORD.

After confirming, the DB connection should work and Matomo will install without any problems.

Initial configuration of Matomo

Before we paste the tracking code into our site, we’d better configure report archiving; if we don’t do this small thing we’ll experience the thrill of a very slow site and high high downtime.

What archiving is: It is simply Matomo processing the collected data and making it visible to us.

By default, archiving is done every so often and triggered by visits from our users. This means that among the requests to stats.mysite.ext/matomo.php there will be some that say “Hey Matomo, start archiving.”

What we want is to disable this behavior and put control in our hands by configuring an automatic process that every tot performs archiving.

Archiving of reports


  1. We access stats.mysite.ext with the SuperUser defined during installation;
  2. We go to the System->General Settings section;
  3. We set the browser-activated storage to No.

Now we need to configure an automatic something. We have a couple of ways:

  • Define a sidecar container called matomo_cron, with the same image as matomo and possibly the same volumes, with an entrypoint script that essentially does two things:
    • Sleeps for n seconds;
    • Runs the archiving script.
  • Trivially define a cron job on the host system.

In this article we follow the second path, although a bit less elegant.

Cron Job

On the host system and with the user we normally run docker with, we run crontab -e, and paste this stuff:

5 * * * * if [ $(docker inspect -f '{{.State.Running}}' matomo) ]; then docker exec -t matomo on -s "/bin/bash" -c "/usr/local/bin/php /var/www/html/console core:archive --url=https://stats.mysite.ext" www-data; fi >> /home/user/logs/matomo-archive.log

Since we defined it with crontab -e, it will be executed by the current user, and the fields have (in order) this meaning: minutes, hours, days, months, days of the week, command.

If we break up our code:

  • It is executed every hour at minute 05;
  • If matomo is running:
    • Runs the storage for the site, with the user (in the container) www-data;
  • Spits out the output in ~/logs/matomo-archive.log.

The final blank line is in the cron specification and its absence may cause the script to fail.

However, I recommend testing the script right away and verify that everything is ok.


At this point we can paste the tracking code into the site, and enjoy statistics:

  • Homemade;
  • Easily manageable in terms of privacy;
  • Self-updating every hour.

If we want to be cool, we also download Matomo’s mobile app and look at statistics from there as well.

Related Posts