In this post, we’ll demonstrate how to securely set up a Ghost blog using Docker Compose with a focus on network isolation. The stack will consist of three containers: Ghost, MySQL (version 8), and Caddy (as a reverse proxy with automatic SSL certificates provided by Let’s Encrypt). We’ll ensure that only ports 80 and 443 are exposed by Caddy, while MySQL remains isolated on an internal network.

Network Design

  • web network: This public network will host Caddy, allowing external access only through Caddy.
  • internal network: This private network will be used exclusively for communication between the Ghost and MySQL containers, with no external exposure.

Docker Compose Configuration

We’ll define everything in a docker-compose.yml file. This file will configure the services, networks, and volumes required for the setup.

  1. Create the docker-compose.yml file:
services:
  mysql:
    image: mysql:8
    container_name: mysql
    networks:
      - internal
    environment:
      MYSQL_ROOT_PASSWORD: your_root_password
      MYSQL_DATABASE: ghost_db
      MYSQL_USER: ghost_user
      MYSQL_PASSWORD: ghost_password
    volumes:
      - mysql_data:/var/lib/mysql
    restart: always

  ghost:
    image: ghost
    container_name: ghost
    networks:
      - internal
    environment:
      url: https://yourdomain.com
      database__client: mysql
      database__connection__host: mysql
      database__connection__user: ghost_user
      database__connection__password: ghost_password
      database__connection__database: ghost_db
    volumes:
      - ghost_content:/var/lib/ghost/content
    restart: always
    depends_on:
      - mysql

  caddy:
    image: caddy
    container_name: caddy
    networks:
      - web
      - internal
    ports:
      - "0.0.0.0:80:80" # bind only to IPv4
      - "0.0.0.0:443:443" # bind only to IPv4
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    restart: always
    depends_on:
      - ghost

networks:
  web:
    external: true
  internal:
    external: true

volumes:
  mysql_data:
  ghost_content:
  caddy_data:
  caddy_config:
  1. Create the Caddyfile: This file will define the reverse proxy rules for Caddy to forward requests to the Ghost container.
yourdomain.com {
    header {
        # enable HSTS
        Strict-Transport-Security max-age=31536000;

        # disable clients from sniffing the media type
        X-Content-Type-Options nosniff

        # clickjacking protection
        X-Frame-Options DENY
        # keep referrer data off of HTTP connections

        Referrer-Policy no-referrer-when-downgrade
        # Content-Security-Policy: default-src 'self'
    }
    reverse_proxy ghost:2368
}
  1. Start the Services: Run the following command to spin up the services:
docker-compose up -d

Breakdown of the Docker Compose Configuration

  • Networks:
    • web: The Caddy and Ghost containers are part of this network, allowing external access to the Ghost blog only via Caddy.
    • internal: The Ghost and MySQL containers are part of this network, isolating MySQL so that only Ghost can access it.
  • MySQL: The MySQL container is placed exclusively in the internal network, ensuring that it is not exposed to the public. Only the Ghost container can access MySQL via this network.
  • Ghost: The Ghost container is connected to both web and internal networks. It needs access to the internal network to communicate with the MySQL container, and access to the web network so that Caddy can reverse proxy external traffic to it.
  • Caddy: The Caddy container is responsible for handling traffic from ports 80 (HTTP) and 443 (HTTPS) and forwarding it to the Ghost container. It is connected only to the web network, ensuring that it can’t access internal services like MySQL.

Security Considerations

  • Port exposure: Only ports 80 and 443 are exposed to the public, through Caddy. The MySQL container remains isolated and inaccessible from the public internet.
  • Network isolation: By separating the web and internal networks, we prevent unauthorized access to the MySQL database while allowing necessary communication between Ghost and MySQL.
  • SSL/TLS: Caddy automatically manages SSL certificates, ensuring all public communication with the blog is encrypted.

By using Docker Compose and network isolation, this setup ensures that your Ghost blog is secure and only exposes necessary services to the public. All sensitive internal communication (e.g., between Ghost and MySQL) is safely confined to the internal network.