VM: docker: SSL ghost with nginx

In this guide we go through setting up a full SSL ghost blog with a NGINX front.

VM: docker: SSL ghost with nginx
Photo by Florian Krumm / Unsplash
Linux Rocks Every Day

Configuration contortion extremis is this.

The goals.

  • Move unencrypted learn.thinkmelt.com to www.thinkmelt.com with SSL.
  • Setting up a docker w/ ghost/nginx/mysql containers all talking and working together.
  • Overdocument it so it resolves a lot of poorly miswritten guides.
  • I spent about a week trying to master / figure this all out towards something that works.  I hope it helps others.

There are a lot of moving parts the docker engine, the class B plumbing between the containers, the nginx reverse-proxy, and when someone finally does get it to work properly they promptly never share or document how they pulled it off.  However the same people that are adroit to share how they made it work will happily and hypocritically be begging for the working setup expecting everyone to give them answers.  Anyways... Let's get started!

  1. Install docker / docker-ce etc.

Starting with a New Installation of Ubuntu 18.04 Plain:

sudo apt update -y && sudo apt upgrade -y

Right off the hop ubuntu 18.04 held broken packages and was not cleanly upgrading. Reboot and try to update and upgrade again:

sudo reboot

Ubuntu 18.04 as of 2023 / 05 had to update close to 216 packages.

  1. Opening Port 80 / 443
  2. Because docker handles adjustments to iptables automatically - we will not bring another layer of confusion to this by activating ufw (ultimate firewall).
  3. ufw is installed but off by default.
  4. Docker can open the ports automatically in the docker compose.

2. Installing docker / docker -ce from here

Which has been compressed into a single easy script: docker_install.sh

sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin



chmod +x docker_install.sh && ./docker_install.sh

3. Test that docker works

docker run hello-world

4. Install mariadb:10.1 docker-compose.yml

Make a directory in your /root named mariadb and inside it create a file name docker-compose.yml and put this inside of it.

  • Spaces matter notice the 2-space indenting.
  • 3306 is the public port that docker will map and expose to the internal port of the container (3306)
  • We are mapping the directory local of /mysql to the one inside the container of /var/lib/mysql
version: '2'

services:
  mysql:
    image: mariadb:10.1.19
    restart: unless-stopped
    ports:
      - 3306:3306
    volumes:
      - ./mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: 'password'

Once you have your docker-compose.yml typed up activate it with:

docker-compose up

This is temporary (non-daemon mode) as you are not using the -d (daemon mode). We do this initially to make sure it's all working.

docker-compose up -d

5. Install nginx-proxy / Letsencrypt SSL Certificates.

nginx-proxy is the 'messy' part of the configuration - note:

  • If the mysql setup is not perfect for this nginx docker it simply resorts to a SQLite local installation to itself (fine)!
  • We are having it utilize a network named 'nginx-proxy'
  • How is this working nginx is handling the SSL side while ghost is expecting and behaving still in a basic http environment.
docker network create nginx-proxy

Now inside another directory 'nginx' make another docker-compose.yml and put this inside it (note 1 docker-compose.yml per directory)

version: '3'
services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: proxy-manager
    restart: unless-stopped
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./config.json:/app/config/production.json
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - nginx-proxy

volumes:
  data:
  letsencrypt:

networks:
  nginx-proxy:
    external:
      name: nginx-proxy-network

And then the config.json


{
  "database": 
  {
    "engine": "mysql",
    "host": "localhost:3306",
    "name": "root",
    "user": "root",
    "password": "somestrongpassword",
    "port": 3306
  }
  "server":
  {
    "proxy_pass" : "http://107.152.41.231:8080",
    "proxy_set_header" : "X-Forwarded-For $proxy_add_x_forwarded_for",
    "proxy_set_header" : "X-Forwarded-Proto $scheme",
    "proxy_set_header" : "X-Real-IP $remote_addr",
    "proxy_set_header" : "Host $http_host",

  }
}

Once you have that you can stand up the nginx container with a simple

docker-compose up -d

Now set it up (if you screw up your configuration delete the 'data' directory that is local inside the nginx folder.

Defaults for the nginx proxy is admin@example.com password changeme.  As soon as you login you should be able to change it.

Now add a proxy and use this sites configuration for the example:

Do not forget you have it pull a fresh certificate for you from letsencrypt.com which may require setup.

6. Finally install a Container for Ghost Blog

Make a third directory 'ghost' and put a third docker-compose.yml inside it with the following:


version: '3.1'

services:

  ghost:
    image: ghost:4-alpine
    restart: unless-stopped
    ports:
      - 8080:2368
    environment:
      # see https://ghost.org/docs/config/#configuration-options
      database__client: mysql
      database__connection__host: 107.152.41.231
      database__connection__user: 'bob'
      database__connection__password: 'changeme'
      database__connection__database: ghost
      # this url value is just an example, and is likely wrong for your environment!
      url: http://www.thinkmelt.com:8080/
      # contrary to the default mentioned in the linked documentation, this image defaults to NODE_ENV=production (so dev$
      #NODE_ENV: development

ghost blog will have a sorta contorted SSL / to Non-SSL configuration but it will work:

The comment blog section may not work or add users.  Ghost blog seems kinda engineered carefully to break unless you are on the subscription for them.

Linux Rocks Every Day