Ghost-In-A-Box: Live For Two Years

Suddenly, I realized I had boxed our site engine and database over two years ago. Since then, I have never complained about database compatibility, odd Ghost upgrade requirements, or OS product support. It's boring, stable, and predictable.

Of course you could get a hosted plan and a few more subscriptions to add mail delivery and custom integrations. Make it tax deductible and get a nice return. But where is the fun in it?

The biggest challenge was collecting all parameters, variable names, and minor tweaks into a single, working compose file. If interested, you could walk through the compose file below, which includes two images, a few local volumes, and health checks.

Health checks are the key to building a stable stack that starts containers in the appropriate order, when your prerequisites are ready to accept connections. You would think that a MySQL database will give you the most challenging time, but the database image contains mysqladmin CLI that checks service status with a ping command, and it is well documented on Docker Hub.

On the contrary, the Ghost image is very lean and does not contain cURL, wGET or other luxury packages. Luckily, it includes a node executable and http package to help you to check if your container is alive.

💡
You have noticed that my file docker-compose.yml refers to ${SOME_VALUES}. I'm sure you know, that they were declared separatly in the .env file.

When all your components are ready, just run a single command:

$ sudo docker-compose up -f

And your site will be up and available in a few minutes. Faster if you pulled container images before the command. The site upgrade uses the same command but requires one more step.

# Update all images in stack
$ sudo docker-compose pull

# Start updated system
$ sudo docker-compose up -f

---
version: '3.1'

services:
  db:
    image: docker.io/library/mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PWD}
      MYSQL_USER: ${DB_USER_NAME}
      MYSQL_PASSWORD: ${DB_USER_PWD}
    volumes:
     -  ./mysqldb:/var/lib/mysql
     -  ./mysqlinit:/etc/mysql/conf.d:ro
     -  ./backup/ghostdb-dockerd.sql:/docker-entrypoint-initdb.d/chronicler.sql:ro
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_ai_ci
    healthcheck:
       test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
       timeout: 20s
       retries: 10 
     
  ghost:
   image: docker.io/library/ghost:latest
   ports:
      - 2368:2368
   restart: always  
   depends_on:
     db:
       condition: service_healthy
   environment:
     # see https://ghost.org/docs/config/#configuration-options
      database__client: mysql
      database__connection__host: db
      database__connection__port: ${DB_TCP_PORT}
      database__connection__user: root
      database__connection__password: ${DB_ROOT_PWD}
      database__connection__database: ${DB_NAME}
      mail__transport: Direct
      mail__options__service: MailJet
      mail__options__hos: in-v3.mailjet.com
      mail__options_port: 587
      mail__options__auth__user: ${MJ_USER_ID}
      mail__options__auth__pass: ${MJ_USER_PWD}
      mail__from: admin@chronicler.tech
     
      url: https://chronicler.tech/
      server__port: 2368
      logging__info: 'debug'
   volumes:
     - ./content:/var/lib/ghost/content:rw
   healthcheck:
      test: >
        /usr/local/bin/node -e '
             const http = require("http");
             const options = {
                     host: "localhost",
                     port: 2368,
                     path: "/",
                     timeout: 2000
                   };
             const healthCheck = http.request(options, (res) => {
             if ("200,301".includes(res.statusCode) && 
                 "Express" == res.headers["x-powered-by"] )  {
                  process.exit(0);
               } else {
                 process.exit(1);
               }
             });
             healthCheck.on("error", function (err) {
               console.error("GHOST HEALTHCHEC FAILED");
               process.exit(1);
            });
            healthCheck.end();'
      interval: 2m
      timeout: 20s
      retries: 5
      start_period: 3m
...

The file docker-compose.yaml for this site.