Deploying Ghost, MySQL, Let's Encrypt, Nginx Proxy using Docker Compose
My search for the right blogging software wasn't all smooth sailing. It began with the usage suspects - Wordpress, Jekyll (and other markdown based - Hugo), Drupal, Joomla, etc. Eventually I settled for Ghost due to its ease of maintenance and out-of-the-box modern feel. More importantly, I was amazed by the simple setup process (using Docker Compose) and I'm here to write about my experience.
Ghost offers several deployment options namely: direct installation using
npm module on a fresh box or as a docker container. As a developer who have tried several other blogging software and been through some teething problems, I wanted to build a stack that can fulfil my requirements:
- Reproducibility - Moving to another hosting provider should be a breeze
- Scalability - Ability to serve high volume of traffic without breaking
- Security - Free SSL using Let's Encrypt
- Maintenance - Upgrade to newer version should be a breeze
- Easy to Setup - In terms of effort and time
To meet those requirements, I decided to look into docker and immediately stumbled upon the Official Image for Ghost. A glance at the example
stack.yml for Ghost seems promising but something is certainly missing - SSL!
Digging further, it seems like there is no straight forward way to deploy SSL without additional help. The official way of deploying SSL is also done through an integration with Let's Encrypt using the
ghost setup ssl command, not through docker. Faced with this constraint I decided to consult Mr. Google on how other experts solve this problem.
Lo and behold, I didn't have to look far to reach a popular repository by evertramos that claims to offer "Automated docker nginx proxy integrated with letsencrypt". A quick look at the architecture (Fig. 1) seem to suggest that nginx is acting as a reverse proxy to access Website 1 and Website 2. Not to mention that it comes with the ability to handle SSL certificates using Let's Encrypt. Double win!
Digger further, I examined the
docker-compose.yml and found that it runs containers off 3 separate images. After reading their descriptions, it all makes sense.
- nginx - Open source reverse proxy server
- jwilder/docker-gen - Generate files from docker container meta-data
- jrcs/letsencrypt-nginx-proxy-companion - LetsEncrypt companion container for nginx-proxy
Below is my attempt to breakdown the usage of these 3 images and how they are interlinked together. I'm a firm believer in understanding what's going under the hood so when things go wrong, I know where to zoom in.
Reverse Proxy - nginx
Before we introduce the concept of reverse proxy, let's talk about the definition of a proxy - a proxy means that information is going through a third party before getting to the location. Conversely, instead of masking the outgoing connection, the incoming connections will be masked. Your reverse proxy takes care of where that incoming request will go (i.e. Website 1 or Website 2).
One of the important benefits of a reverse proxy is that we can always switch where traffic is served. For example, referencing Fig.1, if I'm taking down Server 1 for maintenance, I can change one line in the reverse proxy and traffic will be served by Server 2. This minimises the downtime and impact to the service.
Another big advantage is that instead of opening up multiple ports to support different services, we only need to open up 80 and 443 for HTTP and HTTPS. All incoming traffic will come in only via these 2 ports. In the eyes of security, this reduces the attack surface and unnecessary worry.
Also, by using a reverse proxy in this architecture, we can host multiple sites simply by adding new containers.
letsencrypt-nginx-proxy-companion will take care of routing the traffic to the right container, and with SSL!
Implementing SSL is a breeze with jrcs/letsencrypt-nginx-proxy-companion. This image automates the creation, renewal and use of Let's Encrypt certificate for proxied Docker containers. It does this with the help of
docker-gen so that whenever a new docker container is spun up,
docker-gen will generate a reverse proxy configuration the same way
nginx-proxy works. For more information, refer to the README.md in the
docker-gen repository and usage instruction of
Putting It Together
After discovering the wonders of https://github.com/evertramos/docker-compose-letsencrypt-nginx-proxy-companion, we are now ready to add in our Ghost container. LuisArteaga (Github user) has kindly created a
docker-compose that leverages on evertramos's awesome stack. Its docker-compose.yml defines 2 services -
db, both on the same
First, to set up docker-compose-letsencrypt-nginx-proxy-companion:
git clone https://github.com/evertramos/docker-compose-letsencrypt-nginx-proxy-companion.git
.env. Note: There is no need to change anything in this
.envfile if you do not need to. For me, since I need to upload theme file in Ghost, I need to change
client_max_body_size's default value of 2M to something higher (100M). This can be done by uncommenting
Next, to start Ghost with a database (docker-ghost-mariadb-letsencrypt):
git clone https://github.com/LuisArteaga/docker-ghost-mariadb-letsencrypt.git
.envand change out the
DB_ROOT_PASSWORDand data path (
docker-compose up -d
Note: As of September 2019, docker-compose.yml will run Ghost using v1.22.2 with MariaDB v10.2.14. You are free to change them to Ghost v2 and MySQL v5.7.
If everything is successful, you should see Ghost running and accessible via browser, with SSL configured automatically!
[2019-09-11 18:09:36] INFO Ghost is running in production... [2019-09-11 18:09:36] INFO Your site is now available on https://derekchia.com/ [2019-09-11 18:09:36] INFO Ctrl+C to shut down [2019-09-11 18:09:36] INFO Ghost boot 4.987s
I hope this article serves you well in setting up your self-hosted blog. If you have any question, feel free to tweet me or contact me at derek[at]derekchia.com.
p.s. I ran the setup on a t2.micro instance (1GB RAM) provided by AWS (free for 1 year) and memory consumption is only ~490MB!