Suggested listen while reading


After years and years of reading blogs to figure out where I went wrong, it’s time for me to share my knowledge with the world, and improve my writing skills along the way.


The process of creating this Ghost instance made me dabble in a few things that I’ve never touched before. Some of these are Digital Ocean and networking on a VPS. I could probably have used DO's own firewall offering, however, I felt like I needed to get some real UFW experience.

But Mr., Docker and UFW are not friends!

I must agree with this, however, I discovered that Ghost can be exposed over a Unix socket, which made my life that much easier. This allowed me drop "plan B", running Nginx in a container.
While there is nothing wrong about running Nginx in a container, the image I use elsewhere has some drawbacks. I always planned on using Nginx on the host itself for multiple reasons, the biggest of them was Fail2Ban. My hard-on for F2B has been up since I implemented it for my Organizr install following this guide.
Another reason for choosing a native Nginx service is certificates. While linuxserver/letsencrypt does allow for letsencrypt certificates it only creates one. Meaning that if I were to host another domain on it, the certificate would show both domains due to SNI.

Now that I have planned my setup, let’s move on to implementing it.

Getting the VPS ready

I am not going to cover the creation of the droplet, but what you need to know is that, at the time of writing, it is running on the cheapest droplet.

When the droplet was finished setting up, I connected to it with the keypair I generated when creating the droplet (I basically just followed DO's excellent Initial Server Setup guide). Once I was done copying the .ssh files I did the mandatory sudo apt upgrade && sudo apt update -y. Then it was time for the fun part! Installing Docker and Docker Compose.

I am lazy. I am saying that because Docker offers an easy way to install it, and I am using it. I used the convenience script to get up and running, which this method has a risk, which is greatly covered in their docs. Now that I shamelessly installed Docker the lazy way it was time for compose, with another one-liner.

Setting up Ghost

Now that I have my prerequisites, I can start building my compose-file.

I went with MariaDB as a database rather than SQLite, because, well it's SQLite. I choose linuxserver/mariadb because that's an image I have worked with previously and it also sets up a database on the initial start-up, which is nice. The environment variables are set according to the documentation for the container.

I am utilizing docker networking for the Ghost container to speak with the database as it's easier and more secure (rather than mapping the port from the database to the host). A better explanation can be found here: linuxserver.io.
This is where I get to use the socket provided by Ghost. The important parts for this to work is mapping the socket to a folder on the host. This can be done with -v /opt/socket/:/opt/socket. Next thing I need is to tell Ghost to create, and use, this socket. I do that with -e server__socket__path=/opt/socket/ghost.socket. This worked, but only for a while, because Ghost creates the socket with a user that Nginx can’t read. So, I had to define which permissions Ghost should create the socket with, and this is luckily something that Ghost supports out of the box, so adding -e server__socket__permissions=0666 solved that.

I set up Mailgun as an e-mail provider, as suggested by Ghost themselves.

Quick tip: create the directories before doing docker-compose up

Installing Nginx and generating a certificate with certbot

I have already stated that I'm lazy, so instead of me repeating DO's guide, I am just linking it for you to follow: Here. Don't worry, I will cover the final Nginx config later in this post.

Now that Nginx is up and running, it's time to get cryptic. I am of course, speaking of SSL.
As mentioned earlier, I am going to use Let's-encrypt as a certificate authority, which is easily done with certbot. You can get certbot through apt, so that's what I'm going to do.

sudo apt-get install certbot python-certbot-nginx python3-certbot-dns-cloudflare

I have this domain under Cloudflare’s DNS servers, which include an api for DNS actions, so I can use that to do the validation that certbot requires. I create a file called ssl.sh in my home directory which is what I will use in order to generate the certificate.

The cf-creds.ini file looks something like this

# Cloudflare API credentials used by Certbot
dns_cloudflare_email = 
dns_cloudflare_api_key = 
cf-creds.ini

On the first run it will ask you to create an "account". This is because Let's-encrypt wants to send you reminder emails when your certificate is about to expire.

We also need to generate a dhparam file in order for Nginx to handle ssl requests.

sudo openssl dhparam -out /etc/nginx/ssl/dhparam-2048.pem 2048

I have a certificate!

Configuring Nginx

Now I am ready to be opening our Ghost instance to the public, and get to use it.

This is the Nginx configuration I ended up with, after playing with it for a good 40 minutes.

This is the file I symlinked in the Nginx guide from Digital Ocean.

Next up is creating the ssl/nonsense.fyi.conf file. I have mine in the same folder as the dhparam file I generated earlier.


I left out my ciphers due to how the gist integration works. Check this link


Now I check my Nginx configuration with sudo nginx -t, and hopefully don't get any errors. Once I have resolved all the errors, I reload Nginx with sudo nginx reload.

I am now ready to create awesome content