How to Host Multiple Ghost Blogs on a Single Server with Docker and Nginx

This guide demonstrates how to host multiple Ghost blogs on a single Ubuntu server using Docker, Nginx as a reverse proxy, and MySQL as the database backend. We’ll also secure the setup with Let’s Encrypt for HTTPS.

In this example, we’ll host two Ghost blogs on the following subdomains:

  • Blog 1: blog.mydomain1.com
  • Blog 2: blog.mydomain2.com

This setup is scalable, allowing you to add more blogs by extending the configuration.

Step 1: Prepare Your Server

  1. Update Your Server Ensure your server is updated and ready:
sudo apt update && sudo apt upgrade -y
  1. Install Docker and Docker Compose Install the necessary tools to run Docker containers:
sudo apt install docker.io -y
sudo apt install docker-compose -y
  1. Set Up a Working Directory Create a directory to hold your configurations:
mkdir docker-dir
cd docker-dir

Step 2: Configure Docker Compose

Create a docker-compose.yml file to define your services. Open the file for editing:nano docker-compose.yml

Insert the following configuration:

version: "3.1"

services:
  reverse-proxy:
    image: nginx
    restart: always
    container_name: reverse-proxy
    volumes:
      - /root/docker-dir/nginx.conf:/etc/nginx/nginx.conf
      - /etc/letsencrypt:/etc/letsencrypt
      - /etc/ssl:/etc/ssl
    ports:
      - 80:80
      - 443:443

  ghost-blog1:
    image: ghost:latest
    restart: always
    container_name: ghost-blog1
    ports:
      - 2368:2368
    volumes:
      - ./ghost-blog1/content:/var/lib/ghost/content
    environment:
      NODE_ENV: production
      database__client: mysql
      database__connection__host: mysql-ghost-db
      database__connection__user: root
      database__connection__password: <password>
      database__connection__database: ghost_blog1
      url: https://blog.mydomain1.com

  ghost-blog2:
    image: ghost:latest
    restart: always
    container_name: ghost-blog2
    ports:
      - 2369:2368
    volumes:
      - ./ghost-blog2/content:/var/lib/ghost/content
    environment:
      NODE_ENV: production
      database__client: mysql
      database__connection__host: mysql-ghost-db
      database__connection__user: root
      database__connection__password: <password>
      database__connection__database: ghost_blog2
      url: https://blog.mydomain2.com

  mysql-ghost-db:
    image: mysql:8.0
    restart: always
    container_name: mysql-ghost-db
    environment:
      MYSQL_ROOT_PASSWORD: <password>
    volumes:
      - ghost-database:/var/lib/mysql

volumes:
  ghost-database:

Save and exit the file.

Step 3: Configure Nginx as a Reverse Proxy

Create an Nginx configuration file to route traffic to the correct Ghost container. Open the file:nano nginx.conf

Insert the following configuration:

events {
    worker_connections 1024;
}

http {
    # Redirect HTTP to HTTPS for blog.mydomain1.com
    server {
        listen 80;
        server_name blog.mydomain1.com;
        location / {
            return 301 https://$host$request_uri;
        }
    }

    # HTTPS server block for blog.mydomain1.com
    server {
        listen 443 ssl;
        server_name blog.mydomain1.com;

        ssl_certificate /etc/letsencrypt/live/blog.mydomain1.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/blog.mydomain1.com/privkey.pem;
        ssl_protocols TLSv1.2 TLSv1.3;

        location / {
            proxy_pass http://ghost-blog1:2368;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
        }
    }

    # Redirect HTTP to HTTPS for blog.mydomain2.com
    server {
        listen 80;
        server_name blog.mydomain2.com;
        location / {
            return 301 https://$host$request_uri;
        }
    }

    # HTTPS server block for blog.mydomain2.com
    server {
        listen 443 ssl;
        server_name blog.mydomain2.com;

        ssl_certificate /etc/letsencrypt/live/blog.mydomain2.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/blog.mydomain2.com/privkey.pem;
        ssl_protocols TLSv1.2 TLSv1.3;

        location / {
            proxy_pass http://ghost-blog2:2368;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
        }
    }
}

Save and exit the file.

Step 4: Set Up HTTPS with Let’s Encrypt

Install Certbot and generate SSL certificates:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot certonly --webroot -w /var/lib/docker/volumes -d blog.mydomain1.com -d blog.mydomain2.com

Reload the Nginx service: sudo systemctl reload nginx

Step 5: Configure DNS

Add the following DNS A records to your domain provider:

  • blog.mydomain1.com → [Your Server IP]
  • blog.mydomain2.com → [Your Server IP]

Step 6: Start the Services

Start your services using Docker Compose: docker-compose up -d

Wrapping Up

You now have a robust setup for hosting multiple Ghost blogs on a single server. This configuration uses Docker, MySQL for data storage, and Nginx as a reverse proxy, all secured with SSL from Let’s Encrypt. You can scale this by adding more Ghost services and updating the Nginx configuration.