Getting the Ghost CMS setup was a pretty painless process with the prebuilt Digital Ocean Droplet. Out of the box, it will server 99% of the majority of users well as a simple blog.

But I never leave things at that, and like to make sure my projects scale. So here’s a walkthrough on getting Ghost 3.x configured as a headless CMS with a React frontend with static assets served via a CDN (Cloudfront). Because why not.

Step 1: Headless CMS

  1. First, setup a Digital Ocean Droplet as you normally would. Im only using the droplet as a dashboard and it will pretty much never be publicly accessible by anyone else, so I spun up the cheapest option at $5/month.

  2. Configure DNS records for your new site. The first one for the droplet should be a subdomain, like cms.yoursitename.com. It doesn’t really matter. It could even be a different domain. We just don’t want your droplet to share the same domain as the live site.

  3. Once you have the blog setup, create an API key for Gatsby. Most tutorials fail to mention how to do this step, or just assume you know how, I guess. Under Integrations in the dashboard, you’ll find an area to near the bottom of the page to generate your keys called Add custom Integration.

Step 2: Build Your blog!

I wanted to use Ghost as a headless CMS. This means the admin is completely separate from the actual front-end of the site. I decided to go with Gatsby (React) for the front-end, since thats what Im most familiar with. And as of late, Ghost has added some really great support for Gatsby. There’s even a starter project. There’s a lot that goes into this step, so I’ll defer to their official documentation here: Build A Custom Static Site With Headless Ghost + Gatsby

Now you’ll also see where your API keys we just created come in handy.

Deploying the Front-end

After you build, there’s a few options deploying your frontend site. The easiest is Netlify, because we can also add a hook that whenever new content gets published, it will initiate a new build and publish our content to the live site. But, you could also set it up on S3, Cloudfront, Github Pages, or really any host of your choice. Services like Heroku and Neflify are some of the few (free) hosts that offer easy to setup hooks to rebuild your static site whenever you publish new content though. Other hosting will require manual uploads or custom hooks.

Setting up a CDN

Even though we’re hosting the static blog somewhere else, the content images are still getting served from our Droplet. It’s probably fine for a small site, but again, I like to build to scale. So let’s get our CDN setup!

We’re going to use ghost-storage-adapter-s3 . I was a little concerned by its age, but it works well. The only downside is it’s not clear how to use it with the latest version (or find it). Plus our droplet is using Ghost in a way that requires us to make a few changes.

SSH into our droplet, switch to our ghost user (sudo -i -u ghost-mgr). Then in /var/www/ghost , we’ll do the following:

	1. `mkdir -p ./content/adapters/storage`

	2. `cd ./content/adapters/storage`

	3. `git clone https://github.com/colinmeinke/ghost-storage-adapter-s3.git s3 `

	4. `git checkout v2.8.0`

	5. `yarn install`

What we’re doing here, is instead of doing a direct NPM install of the adapter, we’re cloning the repo, and switching to a specific branch, then downloading the dependencies and building through yarn. You can probably use npm, but Ive had better luck getting the storage adapter to build with Yarn (Both are already installed on the droplet anyways).

Make sure the index.js file is built. This is also something crucial that the readme’s fail to mention. I’ve found NPM will often times fail at the very end, and not run the build command, whereas Yarn doesn’t.

Now edit your config.production.json file to include the S3 Settings. It will look something like this:

“paths”: {
	“contentPath”: “/var/www/ghost/content”
},
“storage”: {
    “active”: “s3”,
    “s3”: {
      “accessKeyId”: “accessKeyId”,
      “secretAccessKey”: “secretAcessKey”,
      “region”: “us-east-1”,
      “bucket”: “static.alifewellplayed.com”
    }
  },

Make sure to also define your paths if you haven’t already. The Digital Ocean defaults don’t add this, and we need it for Ghost to find or adapter correctly.

Finally you’ll want to run ghost doctor a few times to get everything configured correctly. It will probably make you fix permissions and files being owned by the right user.

From here, we can follow the steps the readme outlines; set up the S3 bucket and IAM policies. You’ll also want to make the bucket publicly readable, and a CORS policy:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Congrats, we’re done! Images you add in Ghost will now be uploaded and served from your S3 bucket instead of your server. But let’s take it one step further, and set up a proper CDN on top of our S3 bucket!

Switch over the the Cloudfront section of AWS, and create a new distribution based on our bucket. Enter a CNAME if you want, otherwise you can also use the random domain generated for you.

If you do want a custom subdomain, you will also need to have a SSL certificate ready, or create one through ACM in AWS. You can also use something like Lets Encrypt too. Either option is free.

Now we can go back to our config file from earlier, and right after our bucket definition line, we’ll add ”assetHost”: “your.cloudfront.cdn“. By the end, it will look like this:

“storage”: {
    “active”: “s3”,
    “s3”: {
      “accessKeyId”: “accessKeyId”,
      “secretAccessKey”: “secretAcessKey”,
      “region”: “us-east-1,
      “bucket”: “static.alifewellplayed.com”,
		”assetHost”: “cdn.alifewellplayed.com“
    }
 },

Wrapping up

There you have it. We have a Headless CMS running on a Droplet, with our static blog with post assets served through a CDN. The ghost-storage-adapter-s3 also offers a few additional settings not mentioned here, like server side encryption for your bucket, or having your settings as environment variables.

There’s also a lot more that can be said about the front-end development with Ghost and Gatsby, but I’ll leave that for a future post.