Setup your own blog with Ghost and Docker

This tutorial walks you through setting up a professional Ghost blog using Docker. We'll go beyond the basic docker run command to cover essential configurations for long-term stability and email integration.

Setup your own blog with Ghost and Docker
tldr; The decision to use Docker is simply a matter of personal preference. While Docker isn't simple, it's a valuable investment for long-term site maintenance, especially when it comes to upgrading your site with the latest libraries and fixes. It will require some effort to handle the initial setup and troubleshooting, but later on, all you'll need to do is run docker pull and docker compose up. So, it's definitely worth a try, right?

Cloud provider

A decent machine is required to run Docker smoothly. I think it would need at least 2 vCPUs and 2GB of RAM for a production Ghost site.

I've spent countless years running websites, from static sites to WordPress and Ghost. Throughout those years, I've also tried several services like AWS, DigitalOcean, and Linode, and I'm currently on Hetzner. The order of these chosen providers already implies my suggestion as of the time of this post.

Mail provider

Although Mailgun is recommended by Ghost team and the UI, I would recommend to use Mailtrap.io as the process to setup one is quite straightforward. You'll need to provide your credit/debit card for verification but it won't cost you anything. (I do not know anything about Mailtrap before setting up my own host at this address).

tldr; I was unable to verify my mobile number with Mailgun. After using sms-pool for one in US, it blocked my account. So don't bother with it if you don't have plan to pay a subscription.

Docker setup

You can definitely ask an AI about steps to setup docker environment on a cloud host. For me, I just followed the steps at https://docs.docker.com/engine/install/ubuntu/

compose.yml

networks:
  ghost_internal:
    name: ghost_internal
  internal-network:
    external: true

services:
  ghost:
    image: ghost:5
    container_name: ghost
    restart: unless-stopped
    environment:
      # URL where your Ghost site will be accessible
      url: https://idealweek.net

      # Database config
      database__client: mysql
      database__connection__host: db
      database__connection__user: ghost
      database__connection__password: <replace_with_your_db_pass>
      database__connection__database: ghost_prod
    deploy:
            resources:
                limits:
                    memory: 512M

    depends_on:
      - db
    volumes:
      - /volumes/ghost:/var/lib/ghost/content
      - /volumes/conf.d/ghost.config.production.json:/var/lib/ghost/config.production.json
    networks:
      - ghost_internal
      - internal-network

  db:
    image: mysql:8
    container_name: ghost_db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: root_pass
      MYSQL_DATABASE: ghost_prod
      MYSQL_USER: ghost
      MYSQL_PASSWORD: <replace_with_your_db_pass>
    deploy:
            resources:
                limits:
                    memory: 512M
    volumes:
      - /volumes/ghost-db:/var/lib/mysql
    networks:
      - ghost_internal

While this may all seem straightforward, there are a few important points to consider:

  • I prefer non-Alpine images because Alpine images often require manual library installations for more complex integrations.
  • It is highly recommended that you map your site's content (/var/lib/ghost/content) to a directory on your host machine. I've had many bad experiences with volume setups that have suddenly become corrupted. The same goes for your database content (/var/lib/mysql).
  • Limit the RAM for your MySQL instance; this is VERY important! The database is known to be a memory hog, so if you don't set a limit, it will consume as much as it can. This can eventually lead to the host OS killing the process.

config.production.json

As defined in the compose.yml, it is mapped to host file so we can update easily. Here is a sample content:

{
  "url": "http://localhost:2368",
  "server": {
    "port": 2368,
    "host": "::"
  },
  "mail": {
    "from": "'Ideal Week' <no-reply@idealweek.net>",
    "transport": "SMTP",
    "options": {
      "service": "Mailtrap",
      "host": "live.smtp.mailtrap.io",
      "port": 587,
      "auth": {
        "user": "api",
        "pass": "<replace_with_your_api_key>"
      }
    }
  },
  "logging": {
    "transports": [
      "file",
      "stdout"
    ]
  },
  "process": "systemd",
  "security": {
    "staffDeviceVerification": true
  },
  "paths": {
    "contentPath": "/var/lib/ghost/content"
  }
}

Bonus

Code syntax highlighting with Prism

Add the snippet below to Settings  Code injection  Site header:

<script src="https://cdn.jsdelivr.net/npm/prismjs/prism.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs/plugins/autoloader/prism-autoloader.min.js" defer></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs/themes/prism.min.css">

Katex integration for Mathematical formula

Add the snippet below to Settings  Code injection  Site header:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.css" integrity="sha384-MlJdn/WNKDGXveldHDdyRP1R4CTHr3FeuDNfhsLPYrq2t0UBkUdK2jyTnXPEK1NQ" crossorigin="anonymous">

<!-- The loading of KaTeX is deferred to speed up page rendering -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/katex.min.js" integrity="sha384-VQ8d8WVFw0yHhCk5E8I86oOhv48xLpnDZx5T9GogA/Y84DcCKWXDmSDfn13bzFZY" crossorigin="anonymous"></script>

<!-- To automatically render math in text elements, include the auto-render extension: -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.15.2/dist/contrib/auto-render.min.js" integrity="sha384-+XBljXPPiv+OzfbB3cVmLHf4hdUFHlWNZN5spNQ7rmHTXpd7WvJum6fIACpNNfIR" crossorigin="anonymous"
        onload="renderMathInElement(document.body);"></script>
  • Inline formula will be between \( and \), for ex: \( x ^{2} \). I intentionally add a space between x and ^ because Ghost will process the ^ character and the formula won't display correctly.
  • Center large display the formula by \[ and \]
  • Use https://latexeditor.lagrida.com/ for composing a formula with ease.