This post is about how I got Ghost running on a Google Compute Engine VM using Docker and Nginx. This powers this blog now, along with a couple of others.
Intro
I’ve spent time recently playing with blogging software again… It seems like every few years I go through this. I’ve run Drupal, I’ve run Wordpress, I had a server hacked in the days long before Cloud that scared me over to PAAS solutions like Blogger and Tumblr for awhile. Then I just used Facebook, then I got quiet on Facebook, then I got sick and we had to use Facebook to tell folks how I was, then I got better and had a lot more friends on Facebook, then I posted something “techy” that got like zero likes and I realized my Facebook friends don’t care when I talk about software engineering, then I remembered I had a blog over here at razorborg.com, and I saw that I actually wrote a couple of interesting posts over the years, but then I realized it was still on Tumblr, which didn’t make a whole lot of sense and I don’t remember how it got there. But I’ve been learning some AWS for work and I’ve been playing with Google Cloud for some time after scoring some credits from attending an I/O a few years back, and I figured, why not, lets see what it takes to move this beast to something a lot more modern, right? So here we are.
I read a bunch of articles. I fired up some VM’s. I tried some Bitnami one-click solutions. I remembered why I hate Wordpress, but why I might end up using it in the next version of my Cub Scout Pack’s website.
Then I found this, which turned out to be the best tutorial I have found for running Ghost (which is gorgeous, and clean, and responsive, and beautiful on a phone, and simple, and just a joy out of the box) on a cloud VM:
https://blog.checkyo.tech/2018/06/28/ghost-docker-tutorial/
Aman, the author, is running on Digital Ocean, a cloud platform that I keep hearing great things about but I haven’t had any professional reason (or free credits) to learn about. I have been playing with Google Cloud, however, and I know it enough to break stuff since I got my kids a cloud Minecraft server running up there last year. So why not try to get it running on Google?
Aman’s tutorial rocked. And if I didn’t find a few minor discrepancies on GCE, I wouldn’t even be writing this post. Let’s see what I did and where we diverged …
1. Create a VM instance and disks
I got this working just fine on a f1-micro
instance. If you’re running a single blog and you don’t get much traffic, I’d say go with that. I eventually resized to “small” after I launched my third Ghost instance on this VM and got the “increase perf” warning from Google. My current setup looks like:
ghost-1
: Machine Type: g1-small, runningUbuntu 18.04
ghost-1
:Boot Disk: 10GB Standard Persistent Diskghost-content-1
: Data/Content Disk: 20GB Standard Persistent Disk (resizable later if necessary)
Note that I created a second persistent disk, with a rule to keep the disk if the VM instance is deleted. This cleanly separates my blog content from the VM startup disk.
2. Install Docker
This tutorial works just fine with Google’s Cloud:
Digital Ocean’s Guide to Installing Docker on Ubuntu 18.04
3. Install Nginx
This is where things start to diverge. Do Step 1 here. Do NOT do Step 2…
Digital Ocean’s Guide to Installing Nginx on Ubuntu 18.04
Google Compute Engine already has a firewall running outside the VM instance, so there is no need to run ufw
. Moreover, if you do enable ufw
and you only add the Nginx rules, you will accidentally turn off SSH access to your host.
Yes, I learned this the hard way.
If you get locked out…
In the unfortunate event that you lock yourself out of your VM because you firewalled the SSH port, there is a way back in.
- Create this bash shell script and put it somewhere on the web (like a storage bucket):
1#!/bin/bash2ufw allow ssh
- Find your VM instance in the cloud console, and click “Edit”
- Under “Custom metadata”, add the key
startup-script-url
with the URL to your script as the value. - Restart your VM
4. Install docker-compose
We are now at “Part 1” of Aman’s tutorial. My VM didn’t have pip
installed, so I went the brute-force way to get docker-compose
, a la this article:
1$ sudo curl -L "https://github.com/docker/compose/releases/download/1.23.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose2$ sudo chmod +x /usr/local/bin/docker-compose
5. Mount your content drive
If you created your content drive brand new when you set up your VM, you will have to format the drive and mount it. Here are Google’s docs:
Adding or Resizing Zonal Persistent Disks
Skip down to Formatting and mounting a zonal persistent disk.
I mounted my disk at /mnt/disks/ghost-content
.
6. Keep following Aman’s tutorial
From here on out, you can follow Aman’s tutorial to get up and running. The steps are basically:
- Create a
docker-compose.yml
file for the site you want to host Note that I am creating these files at/mnt/disks/ghost-content/site-name
- Run the
ghost
container - Configure Nginx
- Add SSL by using
certbot
There’s a lot of chatter in blogs about setting up cron jobs to run certbot
periodically to renew SSL certs from LetsEncrypt. By following the steps in Aman’s tutorial, I was left with a systemd
script that does this automatically.
7. Startup and auto updating the ghost image
Aman’s tutorial ends with a script that updates the ghost
Docker image and restarts it periodically. This is brilliant because that script will run at startup as well, so if you ever need to restart your VM, your site(s) will come right back up.
I extended the update.sh
script a bit more to do this for multiple sites, and it works just fine.
8. Profit
The rest is up to you!
PS. Migrating from Tumblr to Ghost
If you’re most folks, you’re probably migrating from Wordpress. Maybe Blogger. Go you. Somehow I ended up on Tumblr years ago. Tumblr is fine, but it’s use case is really not what this blog is all about.
I found this…
Migrating from Tumblr to Ghost
…and thought, cool, now I have my Tumblr posts in a JSON file! But when I went in import, I discovered my JSON file could only be read by Ghost 1.x, and I was running a 2.x build.
Docker to the rescue…
- I deleted all content on my content disk for this blog
- I updated
docker-compose.yml
to use theghost:1-alpine
image sudo docker-compose up
- Once the Ghost 1.x site was up, the content imported fine
- Shut down the image with
Ctl-C
and revertdocker-compose.yml
to useghost:2-alpine
sudo docker-compose up
again, and the 2.0 image found my content and migrated it to the 2.0 formats. This took a little while. Be patient, it’ll eventually come back up.
PPS: 301 Redirect links from Tumblr URLs
I spent awhile combing through my blog posts and weeding out the bitrot by deleting posts that linked to nowhere. Ah, life on the web. Leave anything alone for awhile, and things start breaking.
Changing platforms often means a change in the default URL structures, and Ghost isn’t very flexible with its URLs. If you have any posts that others may have linked to in the past, please do some due diligence and keep their links alive. If you don’t, they will be annoyed at you one day like I just was annoyed at others.
I found this, which I suspect works great for old Wordpress links with the date in the URLs:
Migrating from Wordpress to Ghost: 301’ing some urls
And hey, I’m running Nginx too! Tumblr URLs take the form post/{post-id}/{post-title}
, so it’s pretty easy to update the redirect regex to handle them. Here’s the line I added to my server > location
section:
1rewrite "post/\d*/(.*)$" /$1 permanent;
That regex says: 1) Look for urls that have post/
followed by some amount of numbers follwed by /
and “take” everything after that slash. Post titles should appear the same in Tumbler and Ghost URLs as long as the title doesn’t change, so we’re good to go!
Afterthoughts and next steps…
I like this setup. I’m planning to run at least 3 sites on this machine, all using Ghost, and I’m pretty happy with where things are.
Eventually I will want to publish out to static pages and front this with a CDN, but I’m not generating enough traffic to worry about that too much.
So now, to start blogging again!