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.
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.