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 > Labsand export all data - SSH into the server and take a copy of the 
contentdirectory. 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