Set up Piwik in Docker

Set up Piwik in Docker

Once you set up a blog and generate some content, you might eventually wonder if anyone is actually reading what your are writing. Down that line of thought is web analytics. After all, it would be nice to know if people are visiting... it gets so lonely out here... hello?... erm... ahem... sorry... Lucky for us there is an app for that. While many use Google Analytics or other proprietary alternatives, today we will be setting up Piwik for our analytics.

Piwik Sounds Delicious!

No, Piwik is not some sort of new exotic fruit - Piwik is a free, open source web analytics software, similar to Google Analytics, but you stay in control of the data. They offer paid hosting, which is a breeze to setup and use, but the Piwik Docker image makes hosting your own easy too.

Setup

Note: This post was written with a version of the Piwik container sometime before Piwik turned into Matomo. The steps may no longer be accurate for a more recent version.

Fortunately, thanks to the power of Docker and containers, setting up Piwik is relatively trivial and should be, for the most part, isolated from your other containers.

I am going to assume you have both Docker Engine and Docker Compose installed. Docker has an excellent documentation section that should be able to handle this better than I. Also assumed is a passing familiarity with docker and docker-compose - nothing too advanced is used, but there is a lot to cover and explaining commands is too consuming.

To get Piwik running we need a database, a Piwik instance, and a web server so you can view your analytics. Optionally, you should probably get a reverse proxy set up as well. If you already have one or do not need one then skip it. There are images on Docker Hub for all these things! We'll use the following images:

Pull the images down now:

docker pull mysql
docker pull ghost
docker pull nginx
docker pull jwilder/nginx-proxy

Docker Compose

Since I will undoubtedly forget the actual incantations to run each container I am going to do something shocking - write it down! Using Docker Compose, we can create docker-compose.yml files that will greatly simplify establishing multiple containers to work with each other. To borrow a phrase from the Internet, Docker Compose is amazeballs.

The Piwik image's page directs you here if you want to use Docker Compose, but I had some trouble with getting their instructions working at the time of this writing. The documentation doesn't mention a few necessary things... the solution was thankfully pieced together by someone else through comments on an issue for the repository. The solution detailed below is similar to and partially derived from that repository, so if you get stuck then try taking a peek in there.

To kick things off, create a folder (wherever you usually create docker directories) named piwik. Inside of it create the following folders and files... they can be empty initially, we are just aiming for the structure right now:

piwik/
  config/ ;; folder
  db/     ;; folder
  docker-compose.yml
  nginx.conf
  revaliases
  ssmtp.conf

Copy Pasta

Rather than properly host the files on Github I have included them below. Certainly not the best idea... It's so wrong but feels so right.

Populate ssmtp.conf with the following and alter as needed (i.e., someEmail@gmail.com is probably not your real email):

www-data:someEmail@gmail.com:smtp.gmail.com:587

Do the same for revaliases, modifying as appropriate:

# The user that gets all the mails (UID < 1000, usually the admin)
root=username@gmail.com

# The mail server (where the mail is sent to), both port 465 or 587 should be acceptable
# See also http://mail.google.com/support/bin/answer.py?answer=78799
mailhub=smtp.gmail.com:587

# The address where the mail appears to come from for user authentication.
rewriteDomain=gmail.com

# The full hostname
hostname=localhost

# Use SSL/TLS before starting negotiation
UseTLS=Yes
UseSTARTTLS=Yes

# Username/Password
AuthUser=username
AuthPass=password

# Email 'From header's can override the default domain?
FromLineOverride=yes
FromLineOverride=yes

For nginx.conf, you can set that up with the following:

user www-data;

events {
  worker_connections 768;
}

http {
  upstream backend {
    server app:9000;
  }

  include /etc/nginx/mime.types;
  default_type application/octet-stream;
  gzip on;
  gzip_disable "msie6";
  
  server {
    listen 80;

    root /var/www/html/;
    index index.php index.html index.htm;

    location / {
      try_files $uri $uri/ =404;
    }

    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
      root /usr/share/nginx/html;
    }

    location = /favicon.ico {
      log_not_found off;
      access_log off;
    }
   
    location ~ \.php$ {
      fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
      fastcgi_param  SERVER_SOFTWARE    nginx;
      fastcgi_param  QUERY_STRING       $query_string;
      fastcgi_param  REQUEST_METHOD     $request_method;
      fastcgi_param  CONTENT_TYPE       $content_type;
      fastcgi_param  CONTENT_LENGTH     $content_length;
      fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
      fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
      fastcgi_param  REQUEST_URI        $request_uri;
      fastcgi_param  DOCUMENT_URI       $document_uri;
      fastcgi_param  DOCUMENT_ROOT      $document_root;
      fastcgi_param  SERVER_PROTOCOL    $server_protocol;
      fastcgi_param  REMOTE_ADDR        $remote_addr;
      fastcgi_param  REMOTE_PORT        $remote_port;
      fastcgi_param  SERVER_ADDR        $server_addr;
      fastcgi_param  SERVER_PORT        $server_port;
      fastcgi_param  SERVER_NAME        $server_name;
      fastcgi_intercept_errors on;
      fastcgi_pass backend;
    }
  }
}

You can leave the folders db and config empty. The containers will have their way with them soon enough.

Nginx Proxy

Adding a reverse proxy via Docker is pretty trivial if you are using nginx-proxy. One thing I found was that it doesn't seem to like being set up with Docker Compose. Probably a case of PEBCAK, but I simply used an ordinary container instead via:

docker run -d --name nginx-reverse-proxy -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy

A result of using this image is that we need to include a VIRTUAL_HOST and expose a port for the server container. This is taken care of in the docker-compose.yml file.

Networks

One of the fun parts of Docker is that we can create networks and add or remove containers as desired. Networking can get complex fast, so I am going to be brief, do a bunch of hand waving, and run away shouting la-la-la-la.

We want to create a network named piwik:

docker network create piwik

If you have a reverse proxy container, add it to the network via:

docker network connect piwik some-reverse-proxy-container

This lets the reverse proxy talk to the containers inside of the piwik network. If it wasn't added then it would be like a middle school dance with boys on one side, girls on the other, and no dancing - in other words, nothing interesting would be happening.

Other notes while on the subject of networking. If you are using a subdomain, make sure DNS is set up properly. Also, make sure your reverse proxy is not getting in the way... at one point, for nginx-proxy, I had a DEFAULT_HOST set and this was giving me a grief as the subdomain kept displaying what was under the DEFAULT_HOST.

SSL

Nice to have so you can be secure when accessing your Piwik client and not reveal any data to those snopping. Haven't gotten this far yet into configuring my Piwik instance, so good luck figuring it out! :-)

It's YML Time!

Without further ado, add the following to the docker-compose.yml file:

version: '2'
services:
  db:
    image: mysql
    volumes:
      - ./db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD
  app:
    image: piwik
    links:
      - db
    volumes:
      - ./config:/var/www/html/config
      - ./ssmtp.conf:/etc/ssmtp/ssmtp.conf
      - ./revaliases:/etc/ssmtp/revaliases
    depends_on:
      - db
  web:
    image: nginx
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    links:
      - app
    networks:
      - default
    expose:
      - "80"
    volumes_from:
      - app
    environment:
      - VIRTUAL_HOST
    depends_on:
      - app

networks:
  default:
    external:
      name: piwik

Things to note:

  • VIRTUAL_HOST and MYSQL_ROOT_PASSWORD both need to be passed in when standing up containers via docker-compose, like MYSQL_ROOT_PASSWORD=your-pw VIRTUAL_HOST=foo.bar.com docker-compose up -d
  • We expose port 80 of the nginx container so the reverse proxy can do its magic. If you have no reverse proxy then you would probably replace expose with:
ports:
  - "80:80"
  • The piwik network we created earlier will be used rather than having a new network created each time we run docker-compose up. Also, when taking down this service, the network will persist since it is external to docker-compose.yml.

Showtime

To get things going, run the below within the same folder as the .yml file:

MY_SQL_ROOT_PASSWORD=your-pw VIRTUAL_HOST=foo.bar.com docker-compose up -d

If you need to take it down for some reason, use docker-compose down. This will stop and remove all containers created. In other words, you'll have to set up Piwik again. If you want to stop a container, but not remove it, then use docker-compose stop... docker-compose start would resume the containers.

Thats a Wrap!

Hopefully when you navigate to the value of VIRTUAL_HOST you will see the initialize page for Piwik. If so then you can continue with the instructions listed on the Piwik image page. If not then take a deep breath... try to think logically about what is not happening:

  • Is it DNS related?
  • Perhaps your reverse proxy isn't picking up the nginx server?
  • Is your firewall blocking port 80?

From here you can continue configuring Piwik via your browser. One important thing to remember is to include the code Piwik provides in your website's header. With luck, you'll even find out people read what you spend hours creating. How thrilling!


Photo by Heather Schwartz / Unsplash