Ghost + Gatsby + Netlify

Introducing the JAMstack... now with more docker!

Ghost + Gatsby + Netlify

Introducing the JAMstack... with docker? 🐳

Ghost has a really cool name which is the sole reason I chose to use it as my headless cms. We'll be using the following to get this up and running:

  • ghost - headless cms
  • gatsby - static site generator that uses react 🎉
  • netlify - free cdn, ci/cd, ssl... and more
  • docker - I don't know why
  • nginx - reverse proxy
  • letsencrypt - ssl, aka that sweet lock icon, for the api (prevent mixed content warnings)
  • digitalocean - for hosting ghost
  • namecheap - domain name

I couldn't figure out how to run certbot in a container for ssl certs so I decided to install nginx and certbot on the server 🤷‍♂️

Get a Domain Name

You need a domain name. I prefer namecheap and have been using them for years

Set up your VPS, or Droplet, on DigitalOcean

There is an image in the market place with docker already installed

Login to your droplet as root and create a user, ghosty, (or whatever), and give that boy a password

adduser ghosty

Add your boy ghosty to the sudo group with the following command

usermod -aG sudo ghosty

Disconnect as root and login as your new user, you'll be using this guy from now on.

You can set up a firewall through the DO admin interface or through ufw on the box (ufw is already set up on our image and allows connections over 22 for ssh and the ports for communication with docker hub). You can set up a firewall on DO by navigating to the networking tab and then firewalls. Create a firewall that allows ssh, http, and https connections. These are over port 22, 80, and 443 respectively.

To do so through ufw enter the following:

sudo ufw enable 80
sudo ufw enable 443

To verify success type:

sudo ufw status

Login to docker hub

docker login

and start pulling random images for fun.


We also need to start using DigitalOcean DNS, you can set this up by following this guide if you chose namecheap, there are instructions for other registrars on that page as well

Choose a subdomain for your ghost installation, mine is for example and point it at your droplet.

Run Ghost

Create a directory on your server to hold your ghost content. We're running ghost in a container that is bundled with a sqlite database and we need a volume for that data as well as images and everything else that makes this stateful, you could choose to use a named volume instead if that's your preference.

mkdir ~/ghost

I've elected to do a docker-compose file rather than try and maintain a 10 line long docker run command

cd ~/ghost
vim docker-compose.yml

Paste the following into the docker compose file

version: '3'
    image: ghost
    restart: always
      - 2368:2368
      NODE_ENV: production
      - /home/ghosty/ghost/content:/var/lib/ghost/content

Replace the url with the subdomain you created earlier and fix the volume path to wherever you chose to hold your ghost content, if you're following along, just replace ghosty with the name of the user you created earlier.

Start up the ghost api

docker-compose up

Install Nginx

sudo apt update
sudo apt install nginx

It should now be running, enter the following command to verify

systemctl status nginx

The output should show that is active and running

Enter your droplet's ip address into a browser and you should be presented with the default nginx welcome page.

Next, let's create a server block for the blog, replace url with your url with subdomain

sudo vim /etc/nginx/sites-available/

Paste the following, replacing the url with yours

server {
  listen 80;


  location / {
    proxy_pass http://localhost:2368;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Forwarded-Proto $scheme;

* Note: proxy_set_header X-Forwarded-Proto $scheme; is needed to prevent an infinite redirect loop in ghost

SSL Configuration

Add the certbot repository and download with the following

sudo add-apt-repository ppa:certbot/certbot
sudo apt install python-certbot-nginx

Certbot helps easily create ssl certifications backed by letsencrypt.

Now lets verify our new nginx config doesn't suck with the following

sudo nginx -t

If it's good run the following to create and install your ssl cert

sudo certbot --nginx

The tool will parse your server blocks to determine what domain you need to obtain the ssl cert for. Follow the prompts and the cert will be created.

You should now be able to view your ghost installation by visiting your url, mine is

Clone Gatsby

Create an empty repository on github and give it a name, I've chosen the name of my blog. On your local machine, using the gatsby cli, run this command, replacing with the name of your new repo:

gatsby new

Now we need to get an api key from ghost to for gatsby to use on builds. Go to your ghost admin panel, and click on integrations. Add a new integration named netlify/gatsby and copy the content api key. Let's add this to .ghost.json in our gatsby repo to look like the following

  "development": {
    "apiUrl": "",
    "contentApiKey": "9cc5c67c3...1455149d0"
  "production": {
    "apiUrl": "",
    "contentApiKey": "9cc5c67c3...1455149d0"

Run the following, replacing with the name of your repo and username with your github username

git remote add origin
git push -u origin master

Netlify Configuration

Now login to netlify using your github account. Choose new site from git and follow all of the prompts, allow netlify application access to your github account and repo. Then build and deploy.

Next you need to follow the instructions for setting up your site with a custom domain. This will involve setting an alias in DigitalOcean DNS.

You'll want to set up a build hook as well so that any time your content changes a new build will be triggered. Under Deploy settings and continuous deployment you'll find build hooks. Create a new one, name it ghost, or whatever, and copy the hook url. Now you'll want to go back to your ghost admin panel, integrations, and find the custom integration you made earlier. Choose add web hook, and site changed for the event. Paste the url in there and save! Now any changes such as new posts, tags, edited content, etc. will trigger a new build and deploy immediately.

* If your site doesn't build automatically inspect your ghost container's logs. If you see an error similar to the following we'll need to update our docker compose file.

ghost_1  | [2019-09-22 21:12:34] WARN Request to failed because of: ETIMEDOUT.

Update the docker-compose.yml to look like this

version: '3'
    image: ghost
    restart: always
    network_mode: host
      - 2368:2368
      NODE_ENV: production
      - /home/coty/ghost/content:/var/lib/ghost/content

The issue is due to the container using the wrong name servers, network_mode: host tells the container to grab whatever the host is using.


100 puh-cent rating accross the board on (that 99 varies I swear)

Perfect ratings on performance, accessibility, best practices and seo from