Photo by Ian Taylor / Unsplash

How to Deploy Multiple Production-Ready Ghost Websites in 5 Minutes Using Docker

сontainerization Sep 10, 2025

Why Ghost + Docker Is a Perfect Match

If you’re building multiple websites or blogs, Ghost is one of the best tools you can pick. It’s fast, modern and made for publishing. But installing Ghost manually on different servers can quickly turn into a headache — you have to deal with Node.js versions, databases, nginx, SSL certificates and so on.

That’s where Docker comes in. Instead of wasting time setting up everything by hand, you can spin up isolated Ghost containers for each site in just minutes.

In this article, I’ll show you how to:

  • Run multiple production-ready Ghost websites at the same time.
  • Keep everything separated (no config conflicts).
  • Deploy in 5 minutes or less with Docker.

We’ll leverage two more tools to make things even easier:

  • nginx-proxy → An automatic reverse proxy that routes traffic to the right container based on your domain.
  • acme-companion → Automatically fetches and renews free SSL certificates from Let’s Encrypt.

We will be using MySQL as it's the only production-ready database officially supported by Ghost. (Ghost has a built-in SQLite option for non-production environment)

Prerequisites

Before we start, make sure you have:

  • A server (your laptop/desktop/whatever) or VPS/VDS (Ubuntu/Debian works fine).
  • Docker and Docker Compose installed.
  • A domain name for each Ghost website you want to run.

That’s it. No complex setup needed.

Step 1: Create Project Folders

Let’s say you want to host two websites:

  • chess-blog.com
  • travel-blog.com

Create the following structure on your server:


project/
├── docker-compose.yml
├── chess-blog/
│   └── config.production.json
├── travel-blog/
│   └── config.production.json
└── db/
    └── init/
        └── init.sql

Step 2: Initialize Databases and Users

We’ll create a script to set up our databases, users and privileges automatically.

db/init/init.sql

-- create databases
CREATE DATABASE IF NOT EXISTS chess_blog_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS travel_blog_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- create users
CREATE USER IF NOT EXISTS 'chess_blog_db_user'@'%' IDENTIFIED BY 'chess_blog_db_user_password';
CREATE USER IF NOT EXISTS 'travel_blog_db_user'@'%' IDENTIFIED BY 'travel_blog_db_user_password';

-- grant privileges
GRANT ALL PRIVILEGES ON chess_blog_db.* TO 'chess_blog_db_user'@'%';
GRANT ALL PRIVILEGES ON travel_blog_db.* TO 'travel_blog_db_user'@'%';

FLUSH PRIVILEGES;

Tip: generate secure passwords with:

openssl rand -base64 12

Step 3: Configure Ghost for Each Website

Ghost requires a config.production.json file for each site. Copy it from /var/lib/ghost/config.production.json inside the Ghost container, then edit it.

We’ll also add email support using Gmail. For Gmail, you need to create an App Password (not your normal login password).

To get one:

  1. Enable 2FA on your Google account.
  2. Go to Google App Passwords (2-Step Verification → App passwords)
  3. Type the name of your app (e.g "chessblog") to create a new app specific password
  4. Put the generated pass into config.production.json as your-app-password in the mail configuration.

Here’s an example config:

{
  "url": "http://localhost:2368",
  "server": {
    "port": 2368,
    "host": "::"
  },
  "mail": {
    "transport": "SMTP",
    "options": {
      "host": "smtp.gmail.com",
      "port": 587,
      "secure": false,
      "auth": {
        "user": "[email protected]",
        "pass": "your-app-password"
      }
    }
  },
  "logging": {
    "transports": ["file", "stdout"]
  },
  "process": "systemd",
  "paths": {
    "contentPath": "/var/lib/ghost/content"
  }
}

Step 4: Create docker-compose.yml

Here’s the full Docker Compose file for two Ghost websites, a shared MySQL database, nginx reverse proxy and automatic SSL.

services:

  # nginx reverse proxy
  nginx-proxy:
    image: nginxproxy/nginx-proxy
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      # The vhost and conf volumes are only required
      # if you plan to obtain standalone certificates
      # - vhost:/etc/nginx/vhost.d
      # - conf:/etc/nginx/conf.d
      - html:/usr/share/nginx/html
      - certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro
    networks:
      - nginx-proxy-network

  # acme companion for automatic SSL
  acme-companion:
    image: nginxproxy/acme-companion
    container_name: nginx-proxy-acme
    environment:
      - [email protected]
    volumes_from:
      - nginx-proxy
    volumes:
      - certs:/etc/nginx/certs:rw
      - acme:/etc/acme.sh
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - nginx-proxy-network

  # ghost chess blog
  ghost-chess-blog:
    image: ghost:5-alpine
    container_name: ghost-chess-blog
    restart: always
    environment:
      VIRTUAL_HOST: chess-blog.com
      LETSENCRYPT_HOST: chess-blog.com
      LETSENCRYPT_EMAIL: [email protected]
      database__client: mysql
      database__connection__host: mysql
      database__connection__user: chess_blog_db_user
      database__connection__password: chess_blog_db_user_password
      database__connection__database: chess_blog_db
    volumes:
      - ./chess-blog/content:/var/lib/ghost/content
      - ./chess-blog/config.production.json:/var/lib/ghost/config.production.json
    depends_on:
      - mysql
    networks:
      - nginx-proxy-network

  # ghost travel blog
  ghost-travel-blog:
    image: ghost:5-alpine
    container_name: ghost-travel-blog
    restart: always
    environment:
      VIRTUAL_HOST: travel-blog.com
      LETSENCRYPT_HOST: travel-blog.com
      LETSENCRYPT_EMAIL: [email protected]
      database__client: mysql
      database__connection__host: mysql
      database__connection__user: travel_blog_db_user
      database__connection__password: travel_blog_db_user_password
      database__connection__database: travel_blog_db
    volumes:
      - ./travel-blog/content:/var/lib/ghost/content
      - ./travel-blog/config.production.json:/var/lib/ghost/config.production.json
    depends_on:
      - mysql
    networks:
      - nginx-proxy-network

  # MySQL database for both blogs
  mysql:
    image: mysql:8.0
    container_name: ghost-mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootsecret
    volumes:
      - ./db/init:/docker-entrypoint-initdb.d
      - ./db/mysql_data:/var/lib/mysql
    networks:
      - nginx-proxy-network

networks:
  nginx-proxy-network:
    external: true

volumes:
  html:
  certs:
  acme:
  # vhost:
  # conf:

Note: we store ghost's content, configurations and database data on the host disk to prevent loss when containers are deleted or recreated and to allows you to edit the configurations easily on your host.

Step 5: Start Ghost with Docker Compose

First, create an external Docker network (so containers can talk to each other securely):

docker network create nginx-proxy

Then start everything:

docker compose up -d

Step 6: Verify Your Websites

If your DNS is set correctly, your websites will be available at:

Ghost admin panels:

  • https://chess-blog.com/ghost/
  • https://travel-blog.com/ghost/

On first login, Ghost will ask you to create a super admin account.

Tip: If you don’t have DNS set up yet, add this to your local /etc/hosts:

cat /etc/hosts

127.0.0.1 chess-blog.com
127.0.0.1 travel-blog.com

By default the database is not accessible from the host, but you can connect to it from within the container:


docker exec -it ghost-mysql mysql -u root -p

Use the docker-compose.yml as a reference — all passwords are specified there.

Why This Setup Rocks

  • Speed: You can create 5–10 Ghost production-ready websites in under 5 minutes.
  • Isolation: Each site runs in its own container (no conflicts).
  • Scalability: Want to move one website to another server? Just copy its folder and run docker compose up -d.
  • Flexibility: You can later add caching, CDN, or Ezoic/AdSense easily.

Final Thoughts

If you’re running multiple projects, using Docker for Ghost is a no-brainer. You save time, keep things clean and scale effortlessly.

Next step? Try deploying 3–5 websites at once, maybe for different niches — AI, travel, food or personal notes. Each will be up and running in minutes.

Tags