Deploying Ghost to Heroku

A Ghost blog on Heroku can be a great way to feel more connected to the software running your online presence, especially if you're already a Node developer. Getting things set up varies wildly from platform to platform and knowing one method inside and out (for example iisnode and IIS on Windows) does not mean deploying to a cloud service will be a cake walk. Over the last week I've deployed Ghost on Red Hat OpenShift, Heroku, AWS Elastic Beanstalk and IBM Bluemix. The best value and ease of set up is definitely with Heroku for this one.

This outlines a few of the pitfalls you may encounter while deploying Ghost on Heroku. This is not meant to be a comprehensive guide. The documentation for how to do this is out there, but unfortunately it is somewhat decentralized. The most valuable information that assisted me in getting this set up were these links:

  1. Ghost on Heroku. The most complete Guide. (Ever.)
  2. The Ghost in the Machine: Setting up a Ghost blog on Heroku
  3. And, if you want a one-click-deploy solution, there's always One-button Heroku deploy for the Ghost blogging platform.

However if you've already followed some of these and you're still having issues with your deploy, maybe these pitfalls I encountered will save you time.


postinstall script

There may be an error message mentioning the JavaScript files have not been built. The Ghost installation instructions specify running grunt init and grunt prod after npm install. These scripts automate quite a few tasks like installing front end components through bower and others. Heroku isn't aware these things need to happen for the app to run. Adding a postinstall script to your package.json will help Heroku to understand.

Your package.json's scripts section should look something like this:

"scripts": {
 "preinstall": "npm install semver && node -e \"require('./core/server/utils/startup-check.js').nodeVersion()\"",
 "start": "node index",
 "test": "grunt validate --verbose",
 "postinstall": "grunt init prod"

Voila! Heroku will now execute these grunt tasks, provided the grunt dependencies are available. For that you need to set the Heroku environment variable NPM _CONFIG _PRODUCTION=false.


If you're seeing an error message such as grunt not found or grunt-cli not found, this may happen because a fair amount of dependencies are declared as devDependencies. Normally a tool such as grunt is assumed to be installed globally, but this doesn't hold true for Heroku's Node.js buildpacks/app containers. The setting NPM _CONFIG _PRODUCTION=false in Heroku environment variables instructs Heroku to use also the devDependencies. Note that there are spaces in my variable name due to formatting restrictions.


So once everything is deployed, you may want to customize your themes by accessing their files. Here's what you need to know about the way themes are handled with Ghost: if you're not super familiar with git, it's complicated. It involves git sub-modules and if git sub-modules are your thing (maybe an upcoming blog topic) then dive right in. In the end I decided I'd rather not have the themes registered as sub-modules, because if I make extensive changes to my theme I don't want them to be overridden Heroku suddenly pulls in a new commit of the Casper sub-modules. (Sub-modules are like separate git repositories inside a git repository which can update independently of the super-repository -- but not exactly?)

For a personal blog which you plan on customizing, I recommend unlinking Casper as a sub-module. Backup the content/themes/casper folder because we will paste it back in when we're done.

  1. Run git rm --cached /content/themes/casper.
  2. Remove any line referencing Casper in .gitmodules file.
  3. Delete any line referencing Casper in .git/config file.
  4. Delete the Casper folder at /content/themes
  5. Update .gitignore by removing lines ignoring the /content folders.
  6. Commit these changes so the delete happens in git as well.
  7. Copy over Casper from your backup to /content/themes

Now your theme files are tracked just like every other Ghost file.

Getting redirected?

This problem is outlined in my StackOverflow question, Why does a custom domain redirect to Basically, I was having a problem with requests for being redirected to It involed phantom redirects by the Chrome browser and an incorrect setting for HEROKU_URL environment variable. Another problem solved with human hours, some diligence, and a helpful Heroku tech support specialist Xavier Riley

Are you experiencing a different issue?

I'd like to expand this post with issues folks are having with this process in 2016 and beyond. Please leave a comment and I'll try to help!