Setting up a Blog Server under Docker

Setting up a Blog Server under Docker
Ghost blogging service

This is part of a series describing how I've set up and maintain the Ghost blogging service in my Docker stack.

With the Docker environment up and running, the first step is to create the directory structure:

mkdir -p ~/docker/ghost
cd ~/docker/ghost

Then we need a Compose file:

nano docker-compose.yml

Into the empty compose file we include:

services:
  ghost-db:
    image: mysql:8.0
    container_name: ghost-db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=choose_a_root_password
      - MYSQL_DATABASE=ghost
      - MYSQL_USER=ghost
      - MYSQL_PASSWORD=choose_a_db_password
    volumes:
      - ./mysql:/var/lib/mysql

  ghost:
    image: ghost:latest
    container_name: ghost
    restart: unless-stopped
    depends_on:
      - ghost-db
    environment:
      - NODE_ENV=production
      - url=https://blog.plainshawk.co.uk
      - database__client=mysql
      - database__connection__host=ghost-db
      - database__connection__database=ghost
      - database__connection__user=ghost
      - database__connection__password=choose_a_db_password   # Must match above
      - mail__transport=SMTP                                   # Optional — see Stage 4
      - mail__from=noreply@plainshawk.co.uk
      - mail__options__host=smtp.yourprovider.com
      - mail__options__port=587
      - mail__options__auth__user=your_smtp_username
      - mail__options__auth__pass=your_smtp_password
    volumes:
      - ./content:/var/lib/ghost/content
    networks:
      - default
      - proxy-net

networks:
  default:
  proxy-net:
    external: true

Note on mail: The SMTP settings are optional for getting started — Ghost will work without them, but you won't get email notifications or member sign-up emails. You can add them later. If you don't want to configure mail now, remove those four mail__ lines.

Then, in Cloudflare, we can add a record to point to the blog server:

TypeNameContentProxy
Ablogyour public IPGrey cloud (DNS only)

We can now use docker compose to download and start the instance, which could take 20 - 30 seconds to initialise.

docker compose up -d

We can check the logs to see when it is stable, looking for a '...completed' log entry.

docker compose logs -f ghost

Next, we need to add a proxy host in NPM. Log into NPM at http://<mac-mini-local-ip>:81 → Proxy Hosts → Add Proxy Host

In the Details tab:

FieldValue
Domainblog.plainshawk.co.uk
Schemehttp
Forward Hostnameghost
Forward Port2368
Cache AssetsOff
Block Common ExploitsOn
Websockets SupportOn

SSL tab:

  • Request new Let's Encrypt certificate
  • Force SSL: On
  • HTTP/2 Support: On

Advanced tab:

nginx

client_max_body_size 50M;
proxy_read_timeout 300s;
proxy_send_timeout 300s;

# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
    proxy_pass http://ghost:2368;
    proxy_cache_valid 200 1d;
    add_header Cache-Control "public, max-age=86400";
}

Save all that and NPM will set up the certificate and complete the proxy.

Complete the Ghost Setup Wizard

Navigate to https://blog.plainshawk.co.uk/ghost — this is the admin panel.

You'll be greeted by the setup wizard:

  1. Create your account — name, email, password
  2. Invite staff — skip this for now if it's just you
  3. Choose a theme — you can change this any time

Your blog's public-facing URL will be https://blog.plainshawk.co.uk.


Key Admin Settings to Configure

Once logged into the Ghost admin panel at /ghost:

Settings → General:

  • Set your publication name and description
  • Upload a logo and icon

Settings → Design:

  • Choose or upload a theme
  • Ghost's default theme (Casper) is clean and fast

Settings → Members (if you want a newsletter/membership site):

  • Enable or disable member sign-ups
  • Configure subscription tiers if needed
  • This is where you'd connect Stripe for paid memberships

Settings → Integrations:

  • Connect Zapier, Unsplash, or other services if needed
  • Generate API keys here if you want to use Ghost as a headless CMS

Stage 8 — Keeping Ghost on the External Volume (Optional)

If you'd prefer Ghost's content (images, themes, uploads) to live on /Volumes/MediaHome rather than the Mac Mini's SSD:

Edit docker-compose.yml and change the volume:

yaml

    volumes:
      - /Volumes/MediaHome/ghost-content:/var/lib/ghost/content

Then recreate the container:

bash

docker compose down
docker compose up -d

Quick Reference

URLPurpose
https://blog.plainshawk.co.ukPublic-facing blog
https://blog.plainshawk.co.uk/ghostAdmin panel