Ghost + Gatsby + Netlify
Introducing the JAMstack... now with more docker!

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 https://www.namecheap.com
Set up your VPS, or Droplet, on DigitalOcean
There is an image in the market place with docker already installed https://marketplace.digitalocean.com/apps/docker.
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.
DNS
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 https://www.digitalocean.com/community/tutorials/how-to-point-to-digitalocean-nameservers-from-common-domain-registrars#registrar-namecheap.
Choose a subdomain for your ghost installation, mine is ghost.cotyhamilton.com 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'
services:
ghost:
image: ghost
restart: always
ports:
- 2368:2368
environment:
NODE_ENV: production
url: https://ghost.cotyhamilton.com
volumes:
- /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/ghost.cotyhamilton.com
Paste the following, replacing the url with yours
server {
listen 80;
server_name ghost.cotyhamilton.com;
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 https://ghost.cotyhamilton.com
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 cotyhamilton.com with the name of your new repo:
gatsby new cotyhamilton.com https://github.com/TryGhost/gatsby-starter-ghost.git
Now we need to get an api key from ghost to for gatsby to use on builds. Go to your ghost admin panel, ghost.cotyhamilton.com/ghost 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": "https://ghost.cotyhamilton.com",
"contentApiKey": "9cc5c67c3...1455149d0"
},
"production": {
"apiUrl": "https://ghost.cotyhamilton.com",
"contentApiKey": "9cc5c67c3...1455149d0"
}
}
Run the following, replacing cotyhamilton.com with the name of your repo and username with your github username
git remote add origin https://github.com/username/cotyhamilton.com.git
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 https://api.netlify.com/build_hooks/5d86cf83d...cefdf6c8 failed because of: ETIMEDOUT.
Update the docker-compose.yml to look like this
version: '3'
services:
ghost:
image: ghost
restart: always
network_mode: host
ports:
- 2368:2368
environment:
NODE_ENV: production
url: https://ghost.cotyhamilton.com
volumes:
- /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.
Conclusion
100 puh-cent rating accross the board on web.dev (that 99 varies I swear)
