Upgrade to Ghost v4 with Docker Compose

When Ghost 4 was released earlier this year, I've looked into the upgrade from the initial v3 based setup in docker-compose. Unfortunately, the container based setup is not officially supported, and there were no upgrade guides available.

Time goes by, and after the 3rd time googling for Ghost 4 Docker upgrade, thankfully I've found more resources. I'll document my learning story in this blog post; if you are only interested in the solution, do the backup and skip to the 2nd attempt.

Preparations: Backup Ghost

  • Navigate to the admin interface into Settings > Labs and export all data
  • SSH into the server and take a copy of the content directory. Also, connect to the database container and take a full DB backup.

First attempt: Docker exec live upgrade

I wanted to perform the same ghost-cli steps shown in the official guide, and later persist the Docker image using the same software versions from v4. This was suggested in this post.

First, ensure that docker-compose.yml is using a pinned image version. I made the mistake of using ghost:latest in my original installation.

  ghost-server:
    image: ghost:3

Second, pull the latest image for v3 and boot into it. The reason is that NodeJS is a custom installation on /usr/local/bin/node and all attempts to upgrade the NodeJS using apt or apk package managers will fail.

$ docker compose pull && docker-compose down && docker-compose up -d

$ docker ps 
$ docker exec -ti <id> bash

node -v

which node
/usr/local/bin/node

npm install -g npm

npm install -g ghost-cli@latest

Changing to the node user and running ghost update led to file permission problems, which are shown as a first command:

$ find ./ ! -path "./versions/*" -type f -exec chmod 664 {} \;

$ su node

$ ghost update

Unfortunately I changed my setup from SQLite as default, to MySQL ... and the detection failed. At this point, I gave up trying to trick the CLI into the container image upgrade.

Second attempt: Docker image upgrade

Stop the containers with docker-compose down and run docker-compose up in foreground to see the CLI and database operations. From there, I figured that every Ghost startup does software and database sanity checks and applies missing DB migrations. Both containers start and Ghost does a cyclic check to verify the database being available.

Using this knowledge, I've edited the docker-compose.yml and tried the upgrade directly.

  ghost-server:
    image: ghost:4
$ docker compose pull && docker-compose down && docker-compose up -d

It worked :-)

Third attempt: MySQL 8 upgrade

MySQL 5.7 is EOL in October 2023, still, I prefer to stay up-to-date with MySQL 8. Ghost added support for MySQL 8 - there is one shortcoming though with a changed authorization plugin causing troubles with plain text passwords.

Thanks to this issue comment, I did not run into problems and added the command directly into docker-compose.yml prior to upgrading the DB base image.

  ghost-db:
    image: mysql:8
    restart: always
    command: --default-authentication-plugin=mysql_native_password

After pulling & restarting the docker-compose setup, everything works like a charm, as you can see :-)

The blog post live preview (screenshot in the header) is amazing. 😍

PS: The full docker-compose.yml looks like this:

version: '3'
services:

  ghost-server:
    image: ghost:4
    restart: always
    ports:
      - 2368:2368
    depends_on:
      - ghost-db
    environment:
      url: https://dnsmichi.at
      database__client: mysql
      database__connection__host: ghost-db
      database__connection__user: root
      database__connection__password: supersecret
      database__connection__database: ghost
    volumes:
      - /docker/ghost/content:/var/lib/ghost/content

  ghost-db:
    image: mysql:8
    restart: always
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: supersecret
    volumes:
      - /docker/ghost/mysql:/var/lib/mysql