Installing Ghost is a fairly simple process, at least if you are using a single server which is very well documented on the Ghost website and a multitude of other sources. However, if you're looking to install Ghost in a high availability environment, information is sadly lacking. In fact, if you search Google for "ghost high availability" you get a single potentially relevant result of an unanswered post on the Ghost forum related to problems installing on a Galera based MySQL cluster.

Our HA stack

  • Designed primarily for PHP/MySQL apps
  • Hosted on Linode SSD based cloud with Ubuntu 14.04
  • Packages and configurations managed with Puppet
  • Moving towards DeployHQ for deployment of all sites, but not quite there yet
  • Currently running: Nginx 1.6, PHP 5.5, Percona XtraDB Cluster 5.6, GlusterFS 3.5

Installation goals

Essential:

  • No single point of failure
  • Integrate fully with existing environment
    • Nginx web server
    • XtraDB Cluster database
    • User content stored on GlusterFS volumes
  • Install and configure any required packages via Puppet

Nice to have:

  • Distribution of MySQL reads and writes
  • Deploy code via Git with DeployHQ

First steps: Node.JS

I have never used Node before a few days ago, but I've heard a lot about it, including generally favourable stories about using it to rapidly prototype web apps.

I generally like to adopt the policy of trying to install the latest and greatest packages available from reliable sources. We're "stuck" with this version of Ubuntu for the next 2 years and they unfortunately rarely push anything out but security updates (with some exceptions). Node is already 5 releases ahead of the packaged version, for example.

I quickly discovered that the best source for Node packages was NodeSource and that the Puppet Node module was using a now deprecated PPA. Not really wanting to rely on my memory to come back and update the Git submodule for Node at some point in the future and then take the time to test and deploy it I decided to roll my own Puppet module. After figuring out the specifics from the NodeSource installer script, it turned out to be incredibly simple. Here is the code for anyone that might find it useful:

class nodejs (
  $ensure = present,
) {

  include apt
 
  apt::source { 'nodesource':
    location => 'https://deb.nodesource.com/node',
    key => '68576280',
    key_source => 'https://deb.nodesource.com/gpgkey/nodesource.gpg.key',
  }
  
  package { ['nodejs']:
    ensure => $ensure,
    require => Apt::Source['nodesource'],
  }

}

Experimenting with git deployment

I cloned the stable git branch and followed the install process found on Github. Then added a simple config to Nginx and opened the site in the web browser, all looked good, I had the blog home page.

Unfortunately, things went downhill from here, the /ghost admin url didn't work and just gave me a blank page.

After a little research I re-ran grunt init with the -v flag and quickly discovered the problem:

Running "shell:bower" (shell) task
...
bower ESUDO         Cannot be run with sudo

I chown'd everything to my local user and tried again and it looked like the bower task had run successfully with the output ending:

bower showdown#v0.3.2-ghost            checkout v0.3.2-ghost

However, I was still unable to get the admin to load. Unable to find any working solution (and perhaps my lack of Node knowledge not helping) I decided to deploy the zip package instead, which worked.

I managed to solve the problem a few days later after some further testing. Manually running npm install bower installs a newer version of bower which correctly checks out all the Ghost submodules that I hadn't realised I was missing the first time round.

Configuring the database

We have database access configured with a dedicated write node and reads distributed across all nodes, this is all controlled via HA Proxy so we connect to either 127.0.0.1:3306 for writes or 127.0.0.1:3307 for reads. At the time of writing I don't believe Ghost supports this configuration. I used a typical MySQL configuration connecting to 127.0.0.1:3306.

On Ghost 0.4.2, I then started to experience the issues that I mentioned earlier in the unanswered forum post. I also posted on the Ghost forum about it, but as these issues are gone as of Ghost 0.5.0 there is probably no reason to go into them in any further detail.

Shared content

Nothing out of the ordinary here, I created a Gluster volume and moved the data, images and themes folders into it and symlinked them. Had this been PHP I would have probably left the themes folder on the local file system, but as Ghost seems to read the theme on start up rather than for every page load it seems easier to keep them in a single location.

Keeping Ghost running

The Ghost Docs suggest a number of ways to keep Ghost running. I opted to use Supervisor as it was easy to create a simple Puppet module to deploy it and add a configuration file for my blog. Supervisor is also a far cleaner way to run multiple blogs, should I wish to in the future, than having to have an init script for every site.

Outstanding issues

I noticed that when I added a cover photo to Ghost, that it only displayed on pages served by the server that had I had been connected to when I saved the settings. Ghost appears to re-render the theme when the settings are saved. I don't have a solution to this currently other than to spam the save button a few times, this also only works while the load balancer is configured in round robin mode.