An Ansible playbook for deploying a self-hosted Matomo stack using Docker, complete with an NGINX firewall, automatic SSL, MariaDB, and a Matomo sidecar for archiving.
- Automatic SSL via certbot + letsencrypt
- Docker & Docker Compose
- Matomo
- Matomo sidecar container for archiving and other cronjobs
- Zero-downtime matomo updates
- Lazydocker for easy container monitoring
- GZIP enabled matomo.js file reducing tracker size from 60kb to 20kb
- NGINX Firewall and Proxy server that has automatic SSL renewals via CertBot
- Dockerized NGINX, Matomo, Matomo Sidecar for archiving & MariaDB
I've tested this playbook on Ubuntu 24.04. It should work on 22.04, but you might come across issues when provisioning the server, refer to Bugs for more.
- A GitHub account that exposes your public SSH key for server connection or add a local public key file by adjusting
group_vars/all/users.yml
- pipenv installed. Installation guide
- Clone the repository and navigate to the Ansible folder.
- Copy the
.env.example
file to.env
and fill in the necessary variables.- This will define your public SSH keys via Github.
- Copy the
vault_pass.example
tovault_pass
- Set your host IP in
hosts/production
.
The server provisioning playbook will run through the roles
and setup users, install nginx, docker, certbot, cronjobs for docker cleanup, a systemd entry to restart docker compose on after the machine reboots.
Setup the python environment via pipenv
- run
pipenv shell
from ansible folder - run
pipenv install
Install ansible requirements
ansible-galaxy install -r requirements.yml
Provision the server
ansible-playbook provision.yml -e "target_host=production"
- This can take a while, but should then finish without errors.
The deployment playbook (deploy.yml) will setup and start docker compose for this stack.
Setting up secrets
- You can adjust the passwords in
templates/db.env.j2
directly or make use of encrypted passwords via ansible vaults - Make sure you've created a new
vault_pass
file and set a secure password inside it - Then run
ansible-vault encrypt ./group_vars/production/secrets.yml
. When runningencrypt
ansible will always ask for the vault_pass you've defined in the previous step. For other commands it will use thevault_pass
file, you can change this in theansible.cfg
. - If you want to edit it run
ansible-vault decrypt FILENAME
oransible-vault edit FILENAME
, it will read the password now from the
This playbook sets up the docker compose stack in /opt/matomo-stack
ansible-playbook deploy.yml -e "target_host=production"
This will:
- Create a
docker-compose.yml
file on the remote machine to run the stack
- This is where nginx, mariadb, matomo (running twice in parallel) and a matomo container for archiving via cronjobs
- Copy the plugins folder
- Create and mount a folder for nginx logs
- Start the stack
-
Run docker compose if it's not running
-
Or restart the matomo_containers in sequence so that the plugins are present
- Navigate to the
matomo_hostname
url (set in.env
) - The credentials should be populated from the
group_vars/production/secrets.yml
file
- Updating via the admin UI works.
- This is dangerous and can cause the app to break, only do this when you know how to resolve issues that can arise.
This should be easily doable by adding another hosts
entry and creating variables for group_vars/staging
- TODO
- As the matomo folder (var/www/html) is mounted in a volume that is used across containers (matomo, nginx, matomo-cron), updating only the image won't be enough. the volume would have to be deleted and re-created.
- The database would also have to be copied as matomo updates oftentimes change the DB permanently
- If your login page is publicly accessible, make sure to change the layout of the admin URL ASAP. I have had numerous instances now that were flagged by Google as phishing websites, which causes a big headache (can be resolved in the Search Console).
- So make sure to customize the logo and favicon in the Matomo admin UI (System -> General Settings -> Branding settings)
- Add your matomo plugins in the
matomo-plugins
folder. I have added an example plugin in there - Once you've added these, re-run the deploy playbook. This will re-create the docker-compose file, which will mount every plugin into the containers running matomo and restart the stack without downtime
- There is a plugin that can be activated to change color of the header color as well (
matomo-plugins/CustomHeaderColor
)
- The
common.config.ini.php.j2
is copied into the matomo docker when running the deployment script - This file is loaded before the config.ini.php, so changes here might be overwritten.
-- As the
config.ini.php
file is overwritten by the Matomo Admin UI itself, you would need to find a custom solution on how to handle these cases. I.e. copying the entire config file into templates and modifying from there, then mounting this file into the config folder of the matomo containers
- There are 2 instances of nginx running
- Config file is copied from
roles/nginx-setup/templates/server.conf.j2
- When changes are made, re-run the provision playbook
- Config file is copied from
templates/matomo.conf
- When changes to this file are made:
- Re-run the deploy playbook
- restart the nginx container manually (this will cause a little downtime)
# on the docker host
su ansibleadmin
cd /opt/matomo-stack
docker compose exec nginx sh
# in the container
nginx -s reload
exit
- Log files are stored in
/opt/matomo-stack/logs
matomo.error.log -> Error log of the matomo application, log level is set to warn
matomo.nginx.access.log -> Access log of the dockerized NGINX instance
matomo.nginx.error.log -> Error log of the dockerized NGINX instance
access.log -> Access log of the NGINX host instance
error.log -> Error log of the NGINX host instnace
-
Environment vars are configured in
templates/db.env.j2
-
Currently there is no playbook or task to get a backup of mariadb
-
I'd recommend manually dumping the database, a folder is mounted in the mariadb container so the dumps would be available on the host machine
- Lazydocker is installed via ansible and available on the server after provisioning by running
lazydocker
- setup playbook to backup the DB to a remote location
- Log file rotation
The configuration file {/var/www/html/config/config.ini.php} has not been found or could not be read
- This happens only after the first deployment, open the URL and configure the instance
error: externally-managed-environment
- Refer to this blog post https://www.jeffgeerling.com/blog/2023/how-solve-error-externally-managed-environment-when-installing-pip3
- If you're running this on Ubuntu 22.04, you might need to change the python version path in
pre_tasks
ofprovision.yml