Setting up Ghost with Docker over a Cloudflare ZeroTrust Tunnel

Learn how to set up a Ghost CMS blog in a Docker container using a Cloudflare ZeroTrust Tunnel for enhanced security. This guide covers server hardening, Docker installation, and setting up Ghost with WatchTower on a Linux VPS for a lightweight, efficient blogging platform.

I thought it would be fitting for the first post on this site to describe how the platform operates. I wanted a lightweight portfolio/blogging platform, and WordPress is way too robust for my needs. Came across Ghost CMS which requires minimal hardware resources and does not come with the extras I don't need.

The end goal of this project is to set up a Ghost blog in a Docker container hosted on a VPS over a Cloudflare Tunnel.


Requirements

The following is required for this project:

  • a VPS/Droplet running Linux (Ubuntu/Debian)
    • Choose a VPS size based on your blog's expected traffic and bandwidth requirements. I opted for a basic VPS through Hetzner with 2GB RAM, 1 Core and 20GB storage, which is plenty for my needs. If you are not sure what to use, get a basic VPS and scale up later if needed.
    • This can also be easily self-hosted from a homelab, although you will want to segment the network and set up a DMZ with no access to the internal network.
  • a domain name
    • I purchased one via Cloudflare, given that Cloudflare tunnels will be utilized, it makes the management of the domain and the tunnel simpler.
  • a Cloudflare account

High-level Overview

Outlining the process:

  1. Set up and harden a Linux machine.
  2. Install docker and docker compose.
  3. Set up Ghost with WatchTower.
  4. Connect the tunnel.
  5. Profit.

Preparing the Host

  1. Generating an SSH keypair
    1. Install PuTTY and launch the PuTTY Key Generator (PuTTYGen.exe)
    2. Generate a new key pair with a strong passphrase, then save both private and public keys on your local machine.
    3. When creating the VPS, set up SSH key authentication using the OpenSSH public key. If your cloud provider allows you, copy-paste it. Otherwise, skip this step for now and we'll get back to it later.
  2. Creating a new user
    1. Access the server (either via password or private key).
    2. Reset the root password to a strong and complex password.
    3. Create a new non-root user that will be used to log in over SSH later.
    4. Append the public SSH key for the new user.
    5. Paste in the public key from PuTTY, then close and save the authorized_keys file with Ctrl+X.
passwd
sudo adduser newuser
su newuser
mkdir ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys
# Paste in the public key, then save and exit the editor.
  1. Changing the SSH port, disabling root login and forcing key authentication
    1. Edit sshd_config with nano.
    2. Change and uncomment the SSH port. For this example, port 22041 will be used.
    3. Change and uncomment PermitRootLogin to no.
    4. Change UsePAM to no.
    5. Close and save with Ctrl+X
sudo nano /etc/ssh/sshd_config
  Port 22 #uncomment change it to 22041 
  PermitRootLogin yes #uncomment and change it to no
  UsePAM yes #change it to no
  PasswordAuthentication no
  1. Updating the firewall
    1. Update UFW to allow traffic on the new SSH port and disable access to port 22.
    2. Restart SSH and UFW to apply the new settings. Port 22 will drop the connection.
    3. Start a new SSH session with the private key on port 22041, authenticate as the new user account, then switch to root.
sudo ufw allow 22041/tcp
sudo ufw deny 22
sudo ufw status
sudo systemctl restart ssh
sudo ufw enable
sudo ufw reload

Hardening the Host

  1. Updates and Essentials
    1. Update packages and upgrade to the latest version.
    2. Install Fail2Ban.
    3. Enable Automatic Updates.
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
  1. Optional Security Settings
    1. Install CrowdSec. This requires a CrowdSec account. CrowdSec is an open-source security monitoring and response tool that analyzes user behavior and traffic on your server to identify and block potential security threats.
    2. Configure Fail2Ban.
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
apt install crowdsec
sudo cscli console enroll <your ID> --overwrite #get the ID from your CrowdSec console's Security Engine

sudo nano /etc/fail2ban/jail.local
  bantime = 1h #update to 2h
  ignoreip = 127.0.0.1/8 ::1 your_client_ip #whitelist your static client IP address (if your IP is static) by replacing 'your_client_ip' with it to avoid potential login bans due to failed attempts
  maxretry = 5 #update to 3
  enable jails by adding enabled = true to each jail defined by '[]'

Installing Docker and Ghost

  1. Install Docker and Docker Compose
    1. Install Docker and Docker Compose.
    2. Verify Docker is running.
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt update
sudo apt install docker-ce
sudo apt-get install docker-compose-plugin
sudo apt-get install docker-compose

docker-compose --version
sudo systemctl status docker
  1. Installing Ghost and WatchTower
    1. Create a folder for the docker compose yml.
    2. Create the compose.yml file.
    3. Spin up docker compose.
    4. Verify that the containers are running. WatchTower is to ensure the containers are automatically updated when a new version is available.
cd ~
mkdir ghost_docker
cd ghost_docker
nano compose.yml
compose.yml:

version: '3'
services:

  ghost:
    image: ghost:latest
    restart: always
    depends_on:
      - db
    environment:
      url: https://yourdomain.com
      database__client: mysql
      database__connection__host: db
      database__connection__user: root
      database__connection__password: SetAStrongPassword
      database__connection__database: ghost
    volumes:
      - ghost:/var/lib/ghost/content
    ports:
      - 7012:2368

  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: SetAStrongPassword
    volumes:
      - db:/var/lib/mysql

  watchtower:
    image: containrrr/watchtower
    restart: always
    environment:
      WATCHTOWER_SCHEDULE: "0 15 05 * * *" 
      WATCHTOWER_CLEANUP: 1
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

volumes:
  ghost:
  db:
docker-compose up -d
docker ps

Ghost, Ghost-DB and WatchTower should be running.

Setting up the Cloudflare Tunnel

A Cloudflare Tunnel creates a secure, encrypted path between your Ghost server and the Cloudflare network, bypassing the public internet. This enhances security and performance by minimizing the exposure of your server's IP address and optimizing content delivery.

  1. ZeroTrust
    1. Sign in to Cloudflare and select ZeroTrust from the left side menu.
    2. Expand Networks > Tunnels and click Create a tunnel.
    3. Use the Cloudflared connector, give it a descriptive name, then select Debian and the CPU architecture.
    4. Run the generated command on the server to connect it.
    5. Select Public Hostname, add the domain and enter the local path for Ghost:
http://0.0.0.0:7012

Conclusion

To wrap up, this first post has walked through the creation of a streamlined blogging platform using Ghost CMS, Docker, and Cloudflare Tunnel. This setup is ideal for anyone seeking a simple yet effective blogging solution. Ghost CMS offers a user-friendly platform without unnecessary extras, and running it in a Docker container on a VPS ensures both efficiency and scalability. The addition of Cloudflare Tunnel adds an extra layer of security, keeping the server's IP hidden and optimizing content delivery.