Ghost on Heroku with S3


Recently, I reconfigured the EyeCue Lab Notebook to use the Ghost blog platform. This article is a compilation of my process notes, written in the tone of a guide for others who may be trying to install in a similar configuration.

Prerequisites

You'll need:

In the rest of this guide, I use the character $ as a convention to denote when a command is to be run in the command prompt. Don't include the $ if you're copying and pasting my commands into Terminal.

Lines that begin with > denote returns from Terminal commands.

I've substituted our private credential information with alphabet strings like abcdefghijkl. You should replace these alphabet strings with your own credentials for the context.

Setup

The guide is roughly divided into these parts:

  1. Set up Heroku and Amazon S3
  2. Assign Heroku Config Variables
  3. Configure Ghost
  4. Deploy to Heroku

Set up Heroku

Make the Heroku app

I used our company's Heroku account to create a new app called eyecuelabnotebook. I added my personal Heroku account as a collaborator to access the repository.

Clone the App Repository

$ heroku git:clone --app eyecuelabnotebook
$ cd eyecuelabnotebook

Make and Attach the Database

$ heroku addons:add heroku-postgresql:dev --app eyecuelabnotebook

Set up Amazon S3

Create an S3 Bucket

Go on over to the Amazon S3 Console and sign in.

Click the blue Create Bucket button at the top left and name your new bucket. I named mine labnotebook.

Next, you need to make the bucket's contents publicly accessible so your viewers can access the assets you upload. Select your new bucket from the list on the left, then click Properties in the top right. Scroll down to the Permissions section below, then click Add Bucket Policy and paste in the following:

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "AllowPublicRead",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::labnotebook/*"
            ]
        }
    ]
}

Create a User with IAM

Next, you need to create a user so Ghost can access and upload to the bucket. Head over to the Amazon IAM console and click the "Users" section on the left sidebar.

Click the blue Create New Users button at the top of the page, then enter a name for the user. I named my user labnotebook.

When you click Create to make the user, you'll be brought to a page that asks you to download credentials. Click Download Credentials to download a .csv file with important information for this user, then dismiss the window and get back to the users list.

Next, we need to grant permissions for our new user. Select the user from the list, then scroll down and expand the "Permissions" category and the "Managed Policies" subcategory. Click Attach Policy then type "AmazonS3FullAccess" into the search and select the result with that title. Click Attach Policy in the bottom right to confirm and set the permissions.

Assign Heroku Config Variables

Set Heroku Config Variables for the Database

We'll need credentials for the database so that Ghost can store and read our blog's data. When we created the database above, Heroku also automatically assigned a config variable with all the information we'll need. Request your app's current config variables with this command:

$ heroku config -s --app eyecuelabnotebook

The return response should include this:

> DATABASE_URL=postgres://abcdefghijklmn:abcdefghijklmnopqrstuvwxyz@ec2-24-24-242-242.compute-1.amazonaws.com:5432/zyxwvutsrqponm
> HEROKU_POSTGRESQL_RED_URL=postgres://abcdefghijklmn:abcdefghijklmnopqrstuvwxyz@ec2-24-24-242-242.compute-1.amazonaws.com:5432/zyxwvutsrqponm

From the previous response, you get the user, password, host, port, and database all folded into one URL. It's in the following format:

postgres://[POSTGRES_USER]:[POSTGRES_PASS]@[POSTGRES_HOST]:[POSTGRES_PORT]/[POSTGRES_DB]

Substitute the above bracketed pieces as variables in a $ heroku config:add command:

$ heroku config:add POSTGRES_USER=abcdefghijklmn POSTGRES_PASS=abcdefghijklmnopqrstuvwxyz POSTGRES_HOST=ec2-24-24-242-242.compute-1.amazonaws.com POSTGRES_PORT=5432 POSTGRES_DB=zyxwvutsrqponm --app eyecuelabnotebook

Set Heroku Config Variables for Amazon S3

Use a text editor to open the credentials.csv file you downloaded above. It should contain data in the following format:

User Name,Access Key Id,Secret Access Key  
"labnotebook",ABCDEFGHIJKLMNOPQRST,abcdefghijklmnopqrstuvwxyz

Write these values into another heroku config:add command in the command prompt:

$ heroku config:add AWS_ACCESS_ID=ABCDEFGHIJKLMNOPQRST AWS_ACCESS_SECRET=abcdefghijklmnopqrstuvwxyz AWS_BUCKET_NAME=labnotebook

Set Heroku Config Variables for Gmail SMTP

Ghost sends notifications and account setup emails. You can read their documentation about mailers here. I chose to use Gmail SMTP because Gmail accounts are free and SMTP is simple. I created a new Gmail account just for this blog, then filled in the credentials with another Heroku config variable:

$ heroku config:add GMAIL_USER=abcdef@gmail.com GMAIL_PASS=abcdefghijklm

Set Heroku Config Variable for Blog URL

I also set up a config variable for the blog's URL. I use it later in the config.js file for Ghost. Run this command to set the BLOG_URL config variable:

$ heroku config:add BLOG_URL=http://eyecuelabnotebook.herokuapp.com

Set Heroku Config Variable for Production Environment

We need to tell Heroku to run Ghost in Production mode (it defaults to development mode) so this command takes care of that:

$ heroku config:add NODE_ENV=production

Configure Ghost

Copy Ghost Files into the App Repository

Download the latest release of Ghost as a .zip, then copy the contents into the eyecuelabnotebook repository we cloned from Heroku in the second step.

At the time of writing, the latest release of Ghost was 0.5.8.

Don't git clone the Ghost repo because it won't include some critical assets, like the default theme.

.gitignore

Don't forget to make a .gitignore file in your app repository. Mine looks like this:

.DS_Store
npm-debug.log  
node_modules  
.tmp/

Install Custom Themes

This step is optional.

I already built our custom theme with another instance of Ghost after porting over our previous blog's Jekyll template, so I added our theme's files into the /content/themes/eyecuelabnotebook directory.

Don't delete the base "casper" theme — Ghost won't allow you to install if it can't find it.

Configure Ghost to Support S3

Install Dependencies

You'll need to install a few modules in order to integrate with S3. One is AWS-SDK, the official AWS SDK for JavaScript. The other is when, a lightweight Promises implementation that handles asynchronous requests and callbacks in Ghost.

Navigate to your repository in the command prompt and install these modules with the following commands:

$ npm install aws-sdk
$ npm install when

Install s3.js

Download my s3.js file and copy it into your repo as /core/server/storage/s3.js.

Special thanks to Marcus Kida and his work on ghost-aws-s3. I forked this and modified it slightly to work with Ghost 0.5.3+.

Make /config.js

Now, we'll tell Ghost to use all the Heroku Config variables we assigned above. Make a file called /config.js and paste in the following:

// # Ghost Configuration
// Setup your Ghost install for various environments
// Documentation can be found at http://support.ghost.org/config/

var path = require('path'),  
        config;

config = {  
        // ### Production
        production: {
                url: process.env.BLOG_URL,

                fileStorage: true,

                mail: {
                        transport: 'SMTP',
                        options: {
                                service: 'Gmail',
                                auth: {
                                        user: process.env.GMAIL_USER,
                                        pass: process.env.GMAIL_PASS
                                }
                        }
                },

                database: {
                    client: 'postgres',
                    connection: {
                        host: process.env.POSTGRES_HOST,
                        user: process.env.POSTGRES_USER,
                        password: process.env.POSTGRES_PASS,
                        database: process.env.POSTGRES_DB,
                        port: '5432'
                    },
                    debug: false
                },

                aws: {
                    accessKeyId: process.env.AWS_ACCESS_ID,
                    secretAccessKey: process.env.AWS_ACCESS_SECRET,
                    bucket: process.env.AWS_BUCKET_NAME,
                    region: process.env.AWS_BUCKET_REGION
                },

                server: {
                    host: '0.0.0.0',
                    port: process.env.PORT
                }
        },

        // ### Development **(default)**
        development: {
                url: 'http://localhost:2368',

                fileStorage: false,

                database: {
                        client: 'sqlite3',
                        connection: {
                                filename: path.join(__dirname, '/content/data/ghost-dev.db')
                        },
                        debug: false
                },

                server: {
                        // Host to be passed to node's `net.Server#listen()`
                        host: '127.0.0.1',
                        // Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
                        port: '2368'
                },

                paths: {
                        contentPath: path.join(__dirname, '/content/')
                }
        }
};

// Export config
module.exports = config;

Modify /core/server/storage/index.js

Open the /core/server/storage/index.js file and replace storageChoice = 'local-file-store' with this:

storageChoice = 's3';  

Deploy Your Blog to Heroku

Prepare for Deployment

Before deploying to Heroku, you will need to build some of Ghost's assets using Grunt:

$ npm install -g grunt-cli
$ npm install
$ grunt init
$ grunt prod

If you get an error about a .npmignore file while trying to run grunt init, try making a blank file called .npmignore at the top level of the repository (/.npmignore).

Deploy to Heroku

This is it! It's time to push the repository to Heroku for deployment. Add and commit your changes to the repository then push to the remote heroku like this:

$ git add .
$ git commit -m "set up Ghost with S3"
$ git push heroku master

Optionally, you can open a separate Terminal window and tail your Heroku logs to monitor the deployment and debug any issues that may arise. Run this command:

$ heroku logs --app eyecuelabnotebook --tail

Conclusion

Great, you're up and running! Now you have a deployment of Ghost on Heroku that uses an Amazon S3 bucket to host your blog assets.

Set up your Ghost blog with the standard setup process at http://YOUR-BLOG-URL/ghost.