Ghost, Matomo, MariaDB and Traefik with SSL stack for personal blogs

I have created a basic docker compose stack for people who want to use Ghost and Matomo with SSL. Read on for additional details, or head straight to my GitHub repository for the code.
Background
For my personal blog (the one you're reading this on) I had previously set up a basic Ghost installation directly on a VPS. This was working fine but was not exactly as I wanted it, as in my testing I had opted to just directly install Ghost onto the VPS. For Ghost updates I would have to SSH into the server and run Ghost's CLI tool directly. Clearly there was room for improvement here.

Later, as I was researching what analytics tool I could use for having a basic understanding of where my site visitors are coming from and what pages they're looking at, I opted to give Matomo (then Piwik) a try. It seems to work well enough, serves my needs, and importantly, does not do funny things with personal data. I am able to anonymize IP addresses even so that I can just look at overall behavior rather than stalk individuals, so to speak.

As I wasn't sure if Matomo was the right-most choice, I looked around to find a Docker container setup I could use to give it a quick try. It took some effort but I found that one worked ok enough, but still had to manually go in and modify parts to get SSL working.
Having these two inherently separate setups running on one VPS also meant that I had some redundancies where there really didn't need to be any. For example, my Ghost setup was using SQLite, and Matomo uses MySQL. This was pushing my (admittedly low specced) VPS instance to its limits, occasionally having it run out of memory. SSL certificates were also handled separately, with my Ghost instance properly re-issuing them when needed, and Matomo's one having to be re-issued manually every 2+ months.
Clearly, there was a lot of room for improvement here.
My Goals
I wanted to create an easy to deploy stack that took care of both Ghost as-well as Matomo. Further, I wanted both to use one shared DB instance. Both also needed to have their certificates automatically renewed when needed. Lastly, both should be automatically updated, at least point updates, as I prefer to do major upgrades myself.
I couldn't find a similar enough setup that wasn't missing at least one of the key requirements, so I set out to make my own. This, too, would help me (slowly) learn to get better at using tools like Docker and Traefik, which also brings me to my caveat; There probably still is a lot of room for improvements, and there may actually be some bugs or other issues.
By making the code available to everyone I hope others may find use in it, but also to possibly provide updates or fixes to make the setup even better. Sharing is, after all, caring.
The Setup
As mentioned before, the key components to this setup are Ghost and Matomo. In addition to those, Traefik is used both for routing and handling SSL certificate (re-)issuing via Let's Encrypt. MariaDB was chosen as the database store. Watchtower automatically updates individual docker containers as new updates are released. Lastly, Bytemark's SMTP image is used to give both Ghost and Matomo out of the box support for sending out emails.
The entire stack is set up in a Docker Compose file, which should be usable straight out of the box with you only having to create your own .env
file containing some of the required information for your specific setup.
Installing
The easiest way to get this stack up and running is to deploy a VPS instance with Docker and Docker Compose pre-installed. I use DigitalOcean which has this very option available, so that's handy. Be sure to also perform additional steps to ensure your server is as secure as you can make it. Think enabling unattended-upgrades
, disabling root and password-based SSH logins, installing fail2ban
, et cetera.
Also, before starting your stack, ensure that all domains point to your server, or issuing SSL certificates will fail.
Running
With your server ready to go and all domains properly configured, all you need to do to run this stack is to run the following command from the folder in which you have the docker-compose.yml
and .env
files located:
docker-compose down && docker-compose up -d
The first time you launch the stack, Ghost will likely not be able to launch properly as it tries to connect to MariaDB immediately, but MariaDB needs a moment to perform some first-launch tasks. Therefore it is probably handy to launch the entire stack without the -d
flag the first time so you can see all of the log outputs, check that SSL certificate issuing worked successfully for example, then stop the stack (ctrl+c
) and re-launch it, but this time with the -d
flag.
Ghost post-install
Fortunately Ghost should immediately be good to go, as its database settings have already been configured and ready to go. You can just head over to yourdomain.com/ghost
to create your account and start blogging.
Matomo post-install
Matomo requires a few additional steps, which Matomo has a handy first-launch wizard for. Visit your Matomo instance and you'll be greeted with this wizard. Follow its steps, and once you get to the database configuration part, enter db
as the database hostname, and the database name, username and password you specified in the DB_NAME
, DB_USER
and DB_PASSWORD
environment variables, respectively.
After this Matomo's Wizard will guide you through creating your first website, which will provide your tracking code that you can inject into your Ghost blog's footer area over at the Code Injection
section of your Ghost dashboard.
Environment variables
I have included an example.env
file that contains all required variables. Let's go through them here.
ACME_EMAIL
The email address you'd like to use for the certificates generated through Let's Encrypt. They also send you an email of certificates that will expire soon to this address, so it's probably handy for you to use (one of) your primary addresses here.
Note: The email address you provide here is also used as the from
email address for emails sent by Ghost. You can easily modify this in the docker-compose
file if you prefer not to use the same address for this.
DOMAINS_BLOG
A comma-separated list of all domains your blog should be accessible from. This is usually just yourdomain.com
and www.yourdomain.com
, but if you happen to have multiple additional domains for the exact same blog, you can enter those here as-well. Traefik will try to obtain certificates for all the domains you specify here.
DOMAINS_MATOMO
A comma-separated list of all domains Matomo should be accessible from. This is both for Matomo's dashboard as-well as its tracking JS file, et cetera. In my case I am using a sub-domain for this, so I entered s.davejansen.com
here. Traefik will try to obtain certificates for all the domains you specify here.
DOMAINS_TRAEFIK
A comma-separated list of all domains your Traefik dashboard should be accessible from. This is actually not necessary to be used, unless you actually enable Traefik's dashboard. This is not enabled by default in the docker-compose
file as it would open up potential security risks for a live environment. Traefik will try to obtain certificates for all the domains you specify here.
BLOG_URL
The URL (including https://
) of your blog. This has to be provided separately as-well to ensure Ghost knows what its real URL is, as it otherwise thinks localhost will work just fine (as it operates behind Traefik). I have opted to separately create a simple ENV variable for this as your DOMAINS_BLOG
variable likely contains more than one domain name, so this way you can choose whichever one you consider to be the primary one here. In my case I entered https://davejansen.com/
here.
DB_ROOT_PASSWORD
What you'd like the root password of your MariaDB instance to be. Be sure to make this something hard to guess, for obvious reasons.
DB_NAME
The name of the database that will be created and used by both Ghost and Matomo.
DB_USER
& DB_PASSWORD
The database username and password combination that will be created for and used by both Ghost and Matomo.
Closing thoughts
As mentioned at the beginning of this post, there will certainly be room for improvement with this setup. But as a first start I think it can serve you well enough if you've been looking for a setup like this. Additionally, one of the nice things about a docker stack like this is that it's very easy to try out and destroy if you no longer need or want it without having to manually uninstall a bunch of stuff.

I have been running this setup for a few weeks now and I am happy to say that it seems to work well so far. Memory usage is much lower compared to my previously duct-taped-together setup, so I don't have to relaunch Matomo every now and then just to be able to apt update
for example (ouch). I'm still pushing the limits of what I probably should do with just a $5/mo instance, but performance seems more than adequate for my current needs, and at least for my blog I don't need much as I rarely get more than a handful of visitors.
You can find the full code right here at my GitHub Repository. Please try it out, fork it, and submit pull requests if you have improvements you'd like to share.
Thank you.