Setting up Ghost

Setting up Ghost
Photo by Tandem X Visuals on Unsplash

Assuming you're using a private Ubuntu Server, Ghost will run in a container for convenience, with a local MySQL instance, and Apache2 as a reverse proxy.

Step 1 - install docker

Instructions at https://docs.docker.com/engine/install/ubuntu/, installing from docker's apt repo:

sudo apt-get update

sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

sudo mkdir -p /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

Test docker with:

sudo docker run hello-world

Step 2 - Setup MySQL User and DB

Create a user "ghost", a database with the same name, and give the ghost user all rights to the ghost database:

CREATE USER 'ghost'@'localhost' IDENTIFIED BY 'passwordxxx';
create database ghost;
GRANT ALL PRIVILEGES ON ghost.* TO 'ghost'@'%';

Step 3 - Configure MySQL to listen to Docker gateway IP

For containers to see services on the host, like MySQL / MariaDB, those services need to listen to the Docker network's gateway IP - 172.17.0.1 by default.

For MySQL, edit the MySQL config file:

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

and change "bind-address" to append the gateway IP address:

bind-address            = 127.0.0.1,172.17.0.1

Some older versions of MySQL don't support multiple different listening addresses, in which case YMMV.

Step 4 - setup the Ghost docker container

Create a folder to store Ghost content outside container, and the docker compose YAML config file:

mkdir ~/ghost

and then create ghost.yaml in folder, replacing XXX as required (assuming you're using a local MySQL database, with a database user "ghost" and database named "ghost", setup in the next step):

version: '3.1'

services:

  ghost:
    image: ghost:latest
    restart: always
    ports:
      - 2368:2368
    volumes:
      - /XXX/ghost/content:/var/lib/ghost/content:z
    environment:
      # see https://ghost.org/docs/config/#configuration-options
      database__client: mysql
      database__connection__host: 172.17.0.1
      database__connection__user: ghost
      database__connection__password: passwordxxx
      database__connection__database: ghost
      url: https://blog.johnswitzerland.com
      # defaults to PROD env unless you override here
      #NODE_ENV: development

Use docker compose to fetch, configure and start the latest Ghost container image:

docker compose -f ghost.yaml up

Step 5 - Apache as a Reverse Proxy

I chose Apache2, NGINX is also an easy substitute.

Apache needs the following modules enabled:

  • headers
  • proxy_http

And config for the site needs to include something like this:

        RequestHeader set X-Forwarded-Proto "https"
        SSLProxyEngine on

        ProxyRequests Off
        <Proxy *>
          Order deny,allow
          Allow from all
        </Proxy>

        ProxyPreserveHost On
        ProxyPass "/" "http://localhost:2368/"
        ProxyPassReverse "/" "http://localhost:2368/"

        <Location />
          Order allow,deny
          Allow from all
        </Location>

Where:

  1. RequestHeader is required to avoid Ghost redirect loops (endless 301/302 responses) - letting Ghost know Apache is handling SSL and not to try and redirect to a secure link
  2. ProxyPreserveHost is required to pass your full Ghost public domain name to Ghost, rather than the internal address specified by ProxyPass