Keep your self-hosted Ghost blog up to date - fully automated

How to automatically keep every docker container up to date. Here we will keep a Ghost blog up to date as an example.

Keep your self-hosted Ghost blog up to date - fully automated
Logos from Ghost, Docker, Watchtower, MySQL

As you may or may not know, this blog is running with Ghost with a docker-compose setup. I have written about this in Self Hosting a Ghost blog with docker-compose, Mailgun setup, and Stripe subscriptions.

Today we are going to take a look at how to keep your blog up to date since this is a big part of self-hosting and often is neglected in these getting started blog articles.

I am focusing on a procedure that will be automated as much as I feel comfortable. The more manual steps you have to be doing the less likely you will keep things up to date. The friction must be as low as possible to achieve this.
And you know what will happen when you do not update your software? The hackers will come! 🧑‍💻

💡
You can also let your Ghost blog be hosted by Ghost itself. As you can see self-hosting is not easy. They will take care of hosting, mails, updating, backups, and more. Concentrate on content and let the support team help you out when in need.

You can signup here: https://ghost.org*

How to keep your docker containers up to date with Watchtower

I stumbled across a project called Watchtower. This is a docker container that will have access to the docker socket and therefore can update your containers on schedule, a specific time.

Logo of Watchtower
Watchtower Logo

Here is the bare minimum docker-compose.yaml for the watchtower container:

version: "3"
services:
  watchtower:
    image: containrrr/watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
simple watchtower docker-compose.yaml

This will update all containers in a 24h rhythm by default from the time you have started this container. At least when your tags are set up correctly, so let's get to that.

How to properly tag containers for Watchtower

Since you are a decent user the tags for your docker containers are at a fixed version. Something like ghost:5.0.0. This is in general a good idea. But Watchtower will not this particular container.

Actually, Watchtower might update the container. Watchtower will check for the docker image digest. The digest is a sha256 hash of that particular image.
For ghost:5.0.0 is 3ba1995d9b7c94c81a76df5ce85312b388f2575f9b3205db8740dd8206d7ce8b.
Often only the first few characters will be used 3ba1995d9b7c which is mostly long enough to not collide.

As long as that digest does not change Watchtower will not update your container. But Ghost and of course others could decide to overwrite that image tag. It is not common but it happens. Reasons may be a broken build that slipt into production or bug fixes.

Check software version policy and update guides

The right way to tag your images highly depends on your used software. Ghost for example has this document about Major Versions & Long Term Support.

You can safely use ghost:4 or ghost:5 in order to get all patch and minor version updates. If you want to stay at a specific patch version you can go with ghost:5.1 for some reason.

If you are not familiar with Major, Minor, or Patch as a version description have a look at the Semantic Versioning, short SemVer.
It is just a popular approach for versioning software in general but also docker images.

https://semver.org/

Now we have Ghost running and keeping up to date. Awesome! But there is another component which you have to take care of.

Your MySQL database. Ghost 5 only supports MySQL 8 in production. So you will at least have mysql:8. But you should do some reading on that topic. Regarding to the MySQL documentation upgrading from 8.0.x to 8.0.z is always supported (Upgrade Paths).
So for MySQL, I would recommend: mysql:8.0.

The last piece of software you should at least take care of is your webserver. For me that is caddy. Therefore I am just running caddy:2.

My personal watchtower setup

Now we have Watchtower running and the tags set up correctly, you could stop here. I will just share my additional configurations for Watchtower.


I have set up a schedule to update my containers from Monday to Thursday at 6 AM UTC. Also, I want to let Watchtower clean up old images to prevent filling up the disk space with not used docker images.

Also, I wanted to have a notification via email when something is updated. Just to know. So here is my more advanced config:

version: '3'

services
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      WATCHTOWER_CLEANUP: "true"
      TZ: "UTC"
      # 0-6 or SUN-SAT: So I want Mo-Fr at 6
      WATCHTOWER_SCHEDULE: "0 0 6 * * 1-5"
      WATCHTOWER_POLL_INTERVAL: 60
      WATCHTOWER_TIMEOUT: "30s"
      WATCHTOWER_NOTIFICATIONS: "email"
      WATCHTOWER_NOTIFICATION_EMAIL_FROM: "${WATCHTOWER_EMAIL_FROM}"
      WATCHTOWER_NOTIFICATION_EMAIL_TO: "${WATCHTOWER_EMAIL_TO}"
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER: "smtp.gmail.com"
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT: 587
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER: "${WATCHTOWER_EMAIL_USER}"
      WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD: "${WATCHTOWER_SMTP_PASSWORD}"
      WATCHTOWER_NOTIFICATION_EMAIL_DELAY: 10
    restart: unless-stopped

Here the .env file for copy-pasting.

# WATCHTOWER
WATCHTOWER_EMAIL_TO=<MAIL>
WATCHTOWER_EMAIL_FROM=<MAIL>
WATCHTOWER_EMAIL_USER=<MAIL>
WATCHTOWER_SMTP_PASSWORD=<PASSWORD>

And here you see the first ever email when the Watchtower container is started with email notifications:

Watchtower 1.4.0
Using notifications: smtp
Checking all containers (except explicitly disabled with label)
Scheduling first run: 2022-06-02 06:00:00 +0000 UTC
Note that the first check will be performed in 10 hours, 51 minutes, 40 seconds

And if an update has been applied you get something like this:

Found new netdata/netdata:latest image (06223ff4cb1f)
Found new ghost:5 image (0285ba33cc2e)
Found new ghost:5 image (0285ba33cc2e)
Stopping /eliora_ghost-eliora_1 (e9780751ac8f) with SIGTERM
Stopping /blog_ghost_1 (bfc4b2d41da8) with SIGTERM
Stopping /netdata (c8e94f42ee83) with SIGTERM
Creating /netdata
Creating /blog_ghost_1
Creating /eliora_ghost-eliora_1
Removing image 60b5698565b2
Removing image c5c7892dbbb9

New images are found and downloaded, then the current containers get stopped, new ones get created and the old image is removed to save space.

Done.

There are more options than I am using. Check the Watchtower documentation for more information.


That's it. You can apply Watchtower to way more than Ghost. I just use Ghost myself and this did work perfectly as an example.

Have a great day!