I've written a couple times already about how to host a Ghost blog on Heroku. I thought, after part 2, that maybe it would be easier the next time I wanted to update the version of Ghost, but it turns out the Ghost team insists on making the kind of changes that make it hard to host a Ghost blog on Heroku. I don't think this is on purpose, although they'd probably prefer you use their hosting solution (Ghost Pro). As always, I think it's worth detailing what I had to do to get it to work so that hopefully other people can do it with less pain.

Prior to this update, I was running Ghost 0.7.0, and I updated to version 0.11.13, which is the last non-1.0 version. I opted not to go to 1.0 because the Ghost team removed Postgres support, in favor of MySQL, which only matters for me because the MySQL integrations on Heroku give you significantly less space at the free tier. My blog was already using 9MB in Postgres, and both of the MySQL apps give you only 5MB at the free tier, so I would've had to pay $10 a month for the next tier up to switch to version 1.0. The Postgres app I'm using, by contrast, gives you 10,000 rows for free (of which I'm using just over 200). So that's A LOT more.

Updating Ghost proper

As documented in the previous posts on this topic, I always deploy a heroku branch to Heroku, so I started by checking out master (git checkout master) then pulling the upstream branch (git pull upstream master). In order to check out the right version, however, you also need the repository tags, so you also need to run git fetch upstream --tags. Then you can switch back to your deployment branch and run git merge 0.11.13. At this point, I had two conflicts, one in Gruntfile.js, for each I just kept the upstream changes, and one that at first was a bit confusing. git status -s showed this:

AD core/client  

core/client was added by me and deleted in the upstream repository? Those both seem wrong. It turns out that...

The admin area is now it's own repo

Which makes sense from a maintenance perspective, but what that means for hosting Ghost on Heroku is another git submodule. Heroku does not run in a git context, so that doesn't work. So that git status line just indicated that my local branch contained that directory, while in the upstream remote, it had been removed (because it's meant to be installed via git submodules). Fortunately, you can just clone Ghost-Admin and then copy it to ghost/core/client. I'm on a mac, so I used cp -R ghost-admin ghost/core/client. I think it might be different on Linux though if I remember correctly. After you copy it, you also need to rm -rf ghost/core/client/.git otherwise the top level ghost repo will still think it's a git submodule.

It took me a little while to figure out which version of Ghost-Admin I needed, but fortunately the release notes for Ghost 0.11.13 include a commit from John Nolan that says "Upgrade Ghost-Admin to 0.11.13." It's nice when the version numbers are synced like that. I used this version, and I haven't had any trouble.

The build steps changed again

Every time I've updated Ghost, the grunt tasks required to build a functional site have changed. Fortunately, it's always been the same fix. It's basically always the init task without the update_submodules task, but it turns out there's a task that already does it, called build (maybe it's been there in previous version too, and I just never noticed it). Edit package.json and set scripts.install to grunt build prod. This will run in the Heroku build after node_modules are installed and will prep the various components of Ghost to be used on your production blog.

Something is wrong with sqlite3@3.0.x

Even after these steps, I couldn't get Ghost to build on Heroku, or indeed, even locally. Something is wrong with the sqlite3 3.0 range. It just doesn't install (it fails in node pre-gyp trying to compile native assets). And I was stuck for longer than I'd really like to admit trying to figure out how to get around this. I tried changing the version in package.json to ^3.1.0, but both Heroku and my local machine insisted on trying to install 3.0.8. I finally realized it was getting this version from npm-shrinkwrap.json, which I had completely forgotten about. That's an npm relic from bygone node versions. Once I removed it, Heroku successfully installed the 3.1.x version, and everything was up and running.

A note on scheduling posts

The ability to schedule posts to be published at a later time was one of the main reasons I wanted to update Ghost, but once I did, I couldn't figure out how to use the feature. It was very hard to find help on Google, primarily because the version of Ghost I was using was so old and all the Google results were about the newer version, for which the UI was considerably different. I originally thought I'd just have an option for that when I clicked the arrow next to "Save Draft," but it still just said "Publish Now" and "Save Draft" as normal. Then I figured maybe I needed to set the "Publish Date" under "Post Settings," but when I tried, I was getting an error message, and still no "Schedule" button. Finally, I realized I was just putting the time in wrong (9:30 instead of 09:30). A dumb mistake, especially when the error message was telling me I needed a valid date, but there you are. In case you aren't sure how to do it, set the "Publish Date" to a (valid) date and time in the future, and then the "Save Draft" dropdown will give you a "Schedule Now" option. One thing I still haven't figured out, however, is how to clear the schedule time and revert the publish date to now. If you clear it and leave the line blank, the "Save Draft" button does give you a "Publish Now" option, but if you hit "Save Draft" and reopen "Post Settings," you'll see that it's reverted to your original schedule date. ¯\(ツ)/¯ Oh and I think I also accidentally scheduled a post for a past date because it said "2 days ago" later the same day I published it.

Admin area notifications

This is not a required step to run Ghost 0.11.13 on Heroku, but it because increasingly annoying to me, so since I had forked Ghost-Admin anyway, I decided to fix it. Every time I opened the Ghost admin area, I'd get a blue banner at the top telling me that support was ending soon for my version of Ghost, and that I could update to version 1.0. This banner had an "x" in the top right to dismiss it, which I would do, and it would go away in my current session, but invariably, the next I opened the admin area, it was there again. I finally checked the console and saw that the GET request it was making when I clicked the "x" was returning a 403 with the message "You do not have permission to dismiss this notification." Which is annoying beyond reason. Turns out this was intended, at least in the past. I set out to fix this myself in Ghost-Admin, and ended up just hiding the banner instead. In Ghost-Admin/app/components/gh-alert.js there's code that maps alert types to color classes. The info type was mapped to blue. I basically changed that to hidden instead. Here's the full code of the original component:

import Component from 'ember-component';  
import computed from 'ember-computed';  
import injectService from 'ember-service/inject';

export default Component.extend({  
    tagName: 'article',
    classNames: ['gh-alert'],
    classNameBindings: ['typeClass'],

    notifications: injectService(),

    typeClass: computed('message.type', function () {
        let type = this.get('message.type');
        let classes = '';
        let typeMapping;

        typeMapping = {
            success: 'green',
            error: 'red',
            warn: 'yellow',
            info: 'blue'
        };

        if (typeMapping[type] !== undefined) {
            classes += `gh-alert-${typeMapping[type]}`;
        }

        return classes;
    }),

    actions: {
        closeNotification() {
            this.get('notifications').closeNotification(this.get('message'));
        }
    }
});

And here's what I changed it to:

import Component from 'ember-component';  
import computed from 'ember-computed';  
import injectService from 'ember-service/inject';

export default Component.extend({  
    tagName: 'article',
    classNames: ['gh-alert'],
    classNameBindings: ['typeClass'],

    notifications: injectService(),

    typeClass: computed('message.type', function () {
        let type = this.get('message.type');
        let classes = '';
        let typeMapping;

        typeMapping = {
            success: 'green',
            error: 'red',
            warn: 'yellow',
            info: 'blue'
        };

        if (typeMapping[type] !== undefined) {
            classes += `gh-alert-${typeMapping[type]}`;
        }

        // If this is just info, don't show it
        if (type === 'info') {
            return 'hidden';
        }

        return classes;
    }),

    actions: {
        closeNotification() {
            this.get('notifications').closeNotification(this.get('message'));
        }
    }
});

And now I don't get that notification every time. It's still there, but I don't have to deal with it.

You might have noticed I also updated the theme for this blog. It's probably worth discussing in a post, but not right at the end of a long post about something only tangentially related. Check back for that update soon!