Run your blog with Ghost, Docker and LetsEncrypt

In this blog post I'll show you how to set up your own blog just like mine with Ghost, Docker, Nginx and LetsEncrypt for HTTPS. You can follow these instructions to kick-start your own blog or find some alternative approaches in the conclusion.

When I decided to start my blog I knew that I wanted it to have a clean and uncluttered theme, no dependencies on an external relational database and that it should allow me to write in Markdown. Markdown is a kind of structured text for writing documentation which gets turned into HTML for viewing.

I heard about Ghost - a blogging platform written in Node.js that used SQLite as a back-end. After trying it out I set it up on my Raspberry Pi and registered a domain name that allowed dynamic DNS updates. From there I started writing 1-3 times per week about the various open-source technologies I was learning.

Background

I used to self-host my blog at home with a domain-name from namecheap.com. Their free dynamic-DNS service allowed me to serve up web content without a static IP address. I ran the blog on my Raspberry Pi which involved producing and maintaining a Dockerfile for ARM. This setup served me well but the uptime started to suffer every time the ISP had a "hiccup" in their network. So I moved it over to a public cloud host to ensure a better uptime.
Follow my current setup

In the next section I'll tell you how my blog is running today and you can follow these steps to set up your own in the same way.
Set up a cloud host

The first step is to set up a cloud host. If you already have a blog or website and you are using a log analyzer or analytics platform then find out where most of your readers are based. For me, the majority of my readers are based in North America on the East Coast so that's where I provisioned a cloud host.

Google Analytics is a good option for analytics and real-time insights.

Here are some good options for hosting providers:

DigitalOcean
Hetzner
Scaleway

The cheapest options will cost you around 5-10 USD / month with Packet costing around 36 USD / month. I do not advise going any cheaper than this. The first two providers listed use VMs so also offer the ability to make snapshots of your host at any time for a small fee. This is a quick way of doing a backup.
Pick an OS

I picked Centos 7. All of the providers above support this flavour at time of writing.
Lock down the configuration

Some providers offer a cloud firewall which should be enough to close off incoming connections. The advantage of a cloud firewall is that if you mess things up you can turn it off easily. Be careful configuring a firewall in software - you don't want to get locked out inadvertently.

Install Docker

The simplest way to install Docker CE on Centos is via the official yum repositories:

yum install docker

If you're using a regular user-account then run usermod i.e.:

sudo usermod -aG docker $USER

Log out and in again so that your user can access the docker command.

Install Nginx on the host - part 1

Nginx is a load-balancer and reverse proxy. We will use it to stand in front of Ghost and offer HTTPS. If you want to run more than one blog later on you can also use Nginx to help with that.

This is a two-part process.

We're installing Nginx directly onto the host for simplicity and lower latency.

sudo yum install nginx

You can take my configuration file and use it as a template - just change the domain name values for your own host.

This configuration does two things:

* Prepares a static folder for LetsEncrypt to use later on port 80
* Sets up a redirect to the HTTPS-enabled version of your site for any calls on port 80
server {
	listen 80;
	server_name domain.ltd;
	location /.well-known/ {
		root /var/www/domain.ltd/;
	}

	location / {
		return 301 https://$server_name$request_uri;
	}
}

Change the hostname etc and place it at /etc/nginx/conf.d/domain.ltd.conf
Obtain a HTTPS certificate from LetsEncrypt

Before we enable Nginx we'll need to obtain a certificate for your domain. HTTPS encrypts the HTTP connection between your users and your blog. It is essential for when you use the admin page.

Note: if you avoid this step your password will be sent in clear-text over the Internet.

Use certbot to get a certificate.

sudo yum install letsencrypt

Install Nginx on the host - part 2

Now that you have a certificate in /etc/letsencrypt/live for your blog you can finish adding the configuration for Nginx.

These lines enable HTTPS for your blog, but remember to personalise the domain replacing domain.ltd with your own domain name.

Edit /etc/nginx/conf.d/default:

server {
	server_name domain.ltd;
	listen 443 ssl;

	location / {
		proxy_pass	http://127.0.0.1:2368;
	    proxy_set_header    X-Real-IP $remote_addr;
	    proxy_set_header    Host      $http_host;
		proxy_set_header X-Forwarded-Proto https;
	    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

	}

	ssl_certificate     /etc/letsencrypt/live/domain.ltd/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/domain.ltd/privkey.pem;
	ssl on;

}

Run the blog with Ghost and Docker

In this step we will run the blog software in a container and configure it with a URL.

Save create.sh and run chmod +x ./create.sh

#!/bin/sh
docker rm -f ghost

docker run --name ghost \
 -p 127.0.0.1:2368:2368 \
 -e url=https://domain.ltd \
 -v /home/user/ghost/content:/var/lib/ghost/content \
 --restart=always \
  -d ghost:1.21.1-alpine
Replace /home/user with your home directory
Replace url= with the URL of your website

You can run the shell script now called ./create.sh and check that the site came up with docker ps. If something appears to have gone wrong then type in docker logs ghost to check on the container. The create.sh shell-script is re-runnable but you only need to run it once - when you restart the machine Docker will automatically restart the container.

Attempt to access the site from the host's shell:

curl 127.0.0.1:2368 -H "Host: domain.ltd"

Enable Nginx

You can now start and enable Nginx, then head over to your URL in a web-browser to test that everything worked.

sudo systemctl enable nginx
sudo systemctl start nginx

If you see an error then type in sudo systemctl status nginx -l to view the logs. A common error is to miss a ; semi-colon from the end of a line in a configuration file.
Register your admin account

You now need to register the admin account on your blog so that you can write new posts.

Head over to the URL of your blog adding the suffix /admin and follow the new user flow.


Update Ghost

The Docker team host an official image for ghost on the Docker Hub. When you want to update to a newer version then check the tags listed and edit the create.sh script and run it again.
Use analytics

You can install Google Analytics on any site you set up for free. It will show you where your audience is located and which pages they are most interested in. I use this data to drive what to blog about next. It also gives you clues as to where your traffic is coming from - was it Docker Weekly? Or that post on Reddit that got me that spike in traffic?

If you have objections to using Google Analytics, then I'd suggest using some kind of log analyzer or aggregator instead such as the ELK stack or matomo.

Use the insights from the analytics to make your blog better.
Renew your HTTPS certificates

Every three months you will need to renew your HTTPS certificates with LetsEncrypt. This is a caveat of using a free service - they will also send you emails when the certificate is close to its expiry date.

You can automate this with a shell-script and cron.

If you have several blogs you can run them all on the same system providing it has enough RAM and disk available. Just follow the steps above on the same host for each blog.