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.
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.