Skip to Content

Production Guide: Deploy Matomo with Docker Compose + Caddy + MariaDB on Ubuntu

A production-oriented Matomo analytics stack with HTTPS, MariaDB, backups, verification, and privacy-focused operations.

Matomo is a practical choice when a business wants useful website analytics without sending visitor behavior into a third-party advertising ecosystem. A typical SysBrix-style deployment is a small Ubuntu VPS running Matomo, MariaDB, and a reverse proxy that terminates TLS, keeps application containers private, and makes backups boring. This guide walks through a production-oriented installation using Docker Compose, Caddy, and MariaDB for teams that need dependable reporting for marketing pages, product documentation, customer portals, or internal dashboards.

The goal is not just to make the Matomo login page appear. The goal is to ship a stack that survives restarts, keeps secrets out of source control, supports routine upgrades, and gives operators a clear verification path before they invite non-technical users. The pattern below keeps the public surface area small, stores persistent data in named volumes, and uses repeatable commands so the deployment can be recreated during a recovery drill.

Architecture and flow overview

The deployment has four moving parts. Caddy listens on ports 80 and 443, obtains TLS certificates automatically, and proxies traffic to the private Matomo container. Matomo serves the PHP application and talks to MariaDB over an internal Docker network. MariaDB stores analytics data, user settings, site definitions, and scheduled report state. A host-level backup job exports the database and captures Matomo configuration so recovery does not depend on an intact container.

Request flow is straightforward: browser traffic reaches https://analytics.example.com, Caddy forwards it to Matomo on the Docker network, and Matomo writes tracking data into MariaDB. Your websites then use the JavaScript tracking snippet that Matomo provides after setup. For privacy-sensitive environments, keep Matomo behind SSO or a VPN for administrators while still allowing the public tracking endpoint only if your consent model requires it.

Prerequisites

  • Ubuntu 22.04 or 24.04 server with sudo access.
  • A DNS record such as analytics.example.com pointing to the server.
  • Ports 80 and 443 open to the internet for Caddy certificate issuance.
  • SMTP details for password resets and scheduled reports.
  • A privacy policy and cookie-consent plan that matches your jurisdiction.

Step-by-step deployment

1) Install Docker, Compose, Caddy, and firewall basics

Start from a patched host. Keep the firewall simple: SSH plus HTTP and HTTPS. Caddy can run directly on the host while Matomo and MariaDB stay inside Docker.

sudo apt update
sudo apt -y upgrade
sudo apt -y install ca-certificates curl gnupg ufw caddy
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
docker --version
docker compose version

If the copy button is unavailable, select and copy the command block manually.

2) Create the application layout and strong secrets

Use a dedicated directory so Compose files, environment values, and backup scripts are easy to audit. Generate secrets on the server and avoid pasting them into tickets or chat logs.

sudo mkdir -p /opt/matomo/{app,backups}
sudo chown -R $USER:$USER /opt/matomo
cd /opt/matomo
umask 077
cat > .env <<'EOF'
MATOMO_DOMAIN=analytics.example.com
MARIADB_DATABASE=matomo
MARIADB_USER=matomo
MARIADB_PASSWORD=replace-with-a-long-random-password
MARIADB_ROOT_PASSWORD=replace-with-a-different-root-password
MATOMO_SMTP_HOST=smtp.example.com
MATOMO_SMTP_PORT=587
[email protected]
MATOMO_SMTP_PASSWORD=replace-with-smtp-password
[email protected]
EOF

If the copy button is unavailable, select and copy the command block manually.

3) Define the Docker Compose stack

This Compose file uses pinned major images, named volumes, a health check, and an internal network. Review image tags during upgrades instead of floating across breaking changes silently.

cd /opt/matomo
cat > docker-compose.yml <&lt'EOF'
services:
  db:
    image: mariadb:11
    restart: unless-stopped
    command: --max-allowed-packet=64MB --innodb-buffer-pool-size=256M
    environment:
      MARIADB_DATABASE: ${MARIADB_DATABASE}
      MARIADB_USER: ${MARIADB_USER}
      MARIADB_PASSWORD: ${MARIADB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
    volumes:
      - matomo_db:/var/lib/mysql
    healthcheck:
      test: ["CMD-SHELL", "mariadb-admin ping -h localhost -uroot -p$${MARIADB_ROOT_PASSWORD} || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5

  matomo:
    image: matomo:5-apache
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      MATOMO_DATABASE_HOST: db
      MATOMO_DATABASE_ADAPTER: mysql
      MATOMO_DATABASE_TABLES_PREFIX: matomo_
      MATOMO_DATABASE_USERNAME: ${MARIADB_USER}
      MATOMO_DATABASE_PASSWORD: ${MARIADB_PASSWORD}
      MATOMO_DATABASE_DBNAME: ${MARIADB_DATABASE}
    volumes:
      - matomo_data:/var/www/html
    ports:
      - "127.0.0.1:8088:80"

volumes:
  matomo_db:
  matomo_data:
EOF
docker compose pull
docker compose up -d

If the copy button is unavailable, select and copy the command block manually.

4) Configure Caddy and finish browser setup

Caddy terminates HTTPS on the host and proxies to the Matomo container. After Caddy reloads, open the URL, complete the installer, enter the MariaDB credentials from .env, create the first admin, and add your first website.

source /opt/matomo/.env
sudo tee /etc/caddy/Caddyfile >/dev/null <&ltEOF
${MATOMO_DOMAIN} {
  encode zstd gzip
  reverse_proxy 127.0.0.1:8088
  header {
    Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    X-Content-Type-Options "nosniff"
    Referrer-Policy "strict-origin-when-cross-origin"
  }
}
EOF
sudo systemctl reload caddy
curl -I https://${MATOMO_DOMAIN}

If the copy button is unavailable, select and copy the command block manually.

The local port mapping in Compose binds Matomo only to 127.0.0.1. Keeping the container on localhost prevents accidental direct exposure while Caddy remains the single public entry point.

5) Add cron archiving and backups

Matomo needs scheduled archive processing for accurate dashboards at scale. Disable browser-triggered archiving in Matomo settings, then run the archive command regularly. Pair that with database dumps and a restore drill.

cd /opt/matomo
cat > backup-matomo.sh <&lt'EOF'
#!/usr/bin/env bash
set -euo pipefail
cd /opt/matomo
source .env
stamp=$(date +%Y%m%d-%H%M%S)
mkdir -p backups
docker compose exec -T db mariadb-dump -u root -p"${MARIADB_ROOT_PASSWORD}" "${MARIADB_DATABASE}" | gzip > "backups/matomo-db-${stamp}.sql.gz"
tar -czf "backups/matomo-files-${stamp}.tar.gz" docker-compose.yml .env
find backups -type f -mtime +14 -delete
EOF
chmod 700 backup-matomo.sh
( crontab -l 2>/dev/null; echo '5 * * * * docker compose -f /opt/matomo/docker-compose.yml exec -T matomo php /var/www/html/console core:archive --url=https://analytics.example.com/ >/var/log/matomo-archive.log 2>&1' ) | crontab -
( crontab -l 2>/dev/null; echo '20 2 * * * /opt/matomo/backup-matomo.sh' ) | crontab -

If the copy button is unavailable, select and copy the command block manually.

Configuration and secrets handling best practices

Treat analytics as sensitive operational data. Matomo may store URLs, search terms, campaign identifiers, IP-derived locations, user IDs, and custom dimensions. Restrict admin accounts, require strong passwords, and avoid embedding raw customer identifiers in tracking calls. If you need user-level analytics, hash or pseudonymize identifiers before they leave the application.

Keep .env readable only by the deployment user, exclude it from Git, and store a copy in your password manager or secret vault. Rotate SMTP and database credentials during incident response, after staff departures, and whenever a credential appears in logs. For regulated environments, document retention periods and configure anonymization features inside Matomo before large-scale tracking begins.

Verification checklist

  • docker compose ps shows Matomo and MariaDB healthy or running.
  • https://analytics.example.com loads over HTTPS with no browser warnings.
  • The Matomo system check reports no critical PHP, database, or cron issues.
  • A test website sends a page view and the visit appears in the real-time dashboard.
  • The archive cron writes recent output to /var/log/matomo-archive.log.
  • A database backup exists, is larger than zero bytes, and can be decompressed.
cd /opt/matomo
docker compose ps
curl -I https://analytics.example.com
sudo tail -n 50 /var/log/matomo-archive.log
ls -lh /opt/matomo/backups
gzip -t /opt/matomo/backups/matomo-db-*.sql.gz

If the copy button is unavailable, select and copy the command block manually.

Common issues and fixes

Caddy cannot issue a certificate

Confirm the DNS record points at this server, ports 80 and 443 are reachable, and no other process is bound to those ports. Use sudo journalctl -u caddy -n 100 --no-pager to see ACME errors.

Matomo redirects to HTTP or the wrong host

Set the trusted host during setup and verify the public URL in Matomo configuration. Keep all browser access through Caddy so Matomo sees a consistent hostname.

Dashboards are slow after traffic grows

Enable cron archiving, increase MariaDB memory within the server budget, and shorten report ranges for non-admin users. For high-traffic sites, move MariaDB to a dedicated managed database or larger disk.

No visits appear after installing the tracking code

Check browser consent settings, ad blockers, content security policy rules, and the Matomo site ID. Use the network tab to confirm the tracking request reaches your Matomo domain.

Backups exist but restores fail

Practice restoring into a temporary server. Verify the SQL dump, the Matomo files archive, DNS changes, and Caddy configuration. A backup that has never been restored is only a hopeful file.

FAQ

Can Matomo replace Google Analytics for business reporting?

For many teams, yes. Matomo covers traffic sources, campaigns, goals, events, ecommerce, dashboards, and custom reports. The main tradeoff is that you operate the platform and own the data model.

Should MariaDB run on the same server?

For small and medium deployments, same-server MariaDB is simple and reliable. Separate it when traffic, compliance, backup windows, or availability requirements justify the added operational cost.

How much CPU and memory do I need?

Start with 2 vCPU and 4 GB RAM for modest traffic. Watch archive duration, database memory, disk growth, and PHP response time before adding more sites or custom dimensions.

Is the tracking endpoint safe to expose publicly?

Yes, the tracking endpoint is normally public, but the admin interface deserves tighter access. Consider SSO, VPN, IP allowlists, and strong account policies for operators.

How long should analytics data be retained?

Match retention to business need and legal requirements. Many teams keep detailed visitor logs for a shorter period and aggregate reports longer. Configure anonymization before collecting sensitive data.

What is the safest upgrade process?

Read Matomo release notes, take a database backup, snapshot the server if available, pull the new image, run the upgrade during a quiet window, and verify tracking plus reports before closing the change.

Internal links

Talk to us

If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.

Contact Us

Header image: Unsplash, no watermark.

Production Guide: Deploy Kanboard with Docker Compose + Caddy + PostgreSQL on Ubuntu
A production-oriented Kanboard deployment with HTTPS, PostgreSQL, backups, verification, and recovery guardrails.