/ Ghost

Adding HTTPS with Let's Encrypt to Ghost with Docker

In Chrome 68 Google decided to show Not Secured next to domains that do not use HTTPS. I wanted to add HTTPS for some time already, but this motivated me more. I also wanted to know how easy or hard it would be.

An example of a domain with Not Secured that does not use HTTPS.

As I described in my previous post I deploy Ghost with Docker, and I wanted to use HTTPS with Docker without the need to configure too many things. Fortunately, there is a companion container for nginx-proxy that I use that makes this very simple.

Adding docker-letsencrypt-nginx-proxy-companion to docker-compose.yml

In the end, it is straightforward, but it took a bit of effort to figure it out how to modify docker-compose.yml. These changes build on top of the file I introduced in my previous post. The main changes are the usage of LETSENCRYPT_HOST and LETSENCRYPT_EMAIL in the ghost service, and the introduction of a new service called letsencrypt. Also, the nginx-proxy service had to be modified, so it supports HTTPS and is discoverable by the companion container. There are a few new volumes used to store certificates and other related files.

version: '3.7'

# It makes sure all containers share one network so they can connect to each other (Ghost connects to a database).
services:
    ghost:
        image: ghost:2.7.1
        container_name: ghost
        # This will make sure the container is started after the server reboots.
        restart: always
        depends_on:
          - db
        # This will make Ghost accessible outside on port 8080.
        ports:
          - 8080:2368
        # This will store all uploaded images in a dedicated volume that survives a restart.
        volumes:
          - ghost-images:/var/lib/ghost/content/images
        environment:
          # Configuring Ghost with env variables.
          # https://docs.ghost.org/v1/docs/config#section-running-ghost-with-config-env-variables
          # It might be desired to configure also a mail service.
          url: "http://your-domain.com"
          database__client: mysql
          database__connection__host: db
          database__connection__user: ghost
          database__connection__password: ghost
          database__connection__database: ghost
          # A host used by nginx-proxy. You can also list variants with www if desired.
          VIRTUAL_HOST: "your-domain.com"
          LETSENCRYPT_HOST: "your-domain.com"
          LETSENCRYPT_EMAIL: "your@email.com"
    db:
        image: mysql:8.0.12
        container_name: mysql-ghost
        restart: always
        environment:
            # This will make root user not accessible until it is set up.
            MYSQL_RANDOM_ROOT_PASSWORD: "yes"
            MYSQL_ONETIME_PASSWORD: "yes"
            # These credentials are not very important because MySQL will not be accessible outside of the service.
            MYSQL_USER: ghost
            MYSQL_PASSWORD: ghost
            MYSQL_DATABASE: ghost
        volumes:
          - ghost-mysql:/var/lib/mysql
    # It makes sure Ghost is accessible from your-domain.com.
    nginx-proxy:
        image: jwilder/nginx-proxy
        container_name: nginx-proxy
        restart: always
        ports:
          - 80:80
          - 443:443
        volumes:
          - conf:/etc/nginx/conf.d
          - vhost:/etc/nginx/vhost.d
          - html:/usr/share/nginx/html
          - certs:/etc/nginx/certs:ro
          - dhparam:/etc/nginx/dhparam
          - /var/run/docker.sock:/tmp/docker.sock:ro
        # It makes this container discoverable by the companion container.
        labels:
          - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
    # It fetches and manages certificates and necessary configuration for SSL.
    letsencrypt:
        image: jrcs/letsencrypt-nginx-proxy-companion
        container_name: nginx-proxy-le
        restart: always
        depends_on:
          - nginx-proxy
        volumes:
          - vhost:/etc/nginx/vhost.d
          - html:/usr/share/nginx/html
          - certs:/etc/nginx/certs
          - dhparam:/etc/nginx/dhparam:ro
          - /var/run/docker.sock:/var/run/docker.sock:ro

# All used volumes need to be defined.
volumes:
    ghost-images:
    ghost-mysql:
    conf:
    vhost:
    html:
    certs:
    dhparam:

After making the necessary changes to the docker-compose.yml, it is enough to deploy it with:

docker-compose up -d

Redirecting from non-www URL to www

Before I introduced HTTPS, I used to redirect from non-www URL to www with GoDaddy redirects, because it was easy, and it required no configuration on the server. Unfortunately, GoDaddy redirects do not work with HTTPS, so I was forced to make changes on the server.

The first necessary thing was to create a file named after a domain that will be placed in /etc/nginx/vhost.d. In my case a file called tomaslinhart.com.

if ($request_uri !~ "^/.well-known/acme-challenge") {
    return 301 https://www.tomaslinhart.com$request_uri;
}

It is important to omit acme-challenge so it works correctly with JrCs/docker-letsencrypt-nginx-proxy-companion

Once you create the file it needs to be put in the configuration folder. This can be achieved using a dummy Docker container.

docker run -v vhost:/vhost --name helper busybox true
docker cp tomaslinhart.com helper:/vhost/tomaslinhart.com
docker rm helper

After that is enough to restart containers, and the change should be visible.

Tomáš Linhart

Tomáš Linhart

Principal iOS Engineer at Lesson Nine GmbH • Swift & iOS and Mac lover and enthusiast •  • A bit .NET, Android lover...

Read More