Deploy Ghost blog to Cloud Foundry

Ghost is a lovely blogging application that is open source, requires only Node.js, a SQL database and an optional STMP/email service. Perfect for running on Cloud Foundry. So we migrated the static/jekyll Stark & Wayne website to Ghost and its now running on Pivotal Web Services. Here’s how…

ghost-demo

Follow these instructions to install Node.js & Ghost on your local environment.

Add the cf-env & pg npm packages to package.json:

    "dependencies": {
        ...
        "pg": "3.0.3",
        "cf-env": "~0.1.1"
    },

Then install all the packages:

npm install --production

When Ghost, a Node.js app, runs on Cloud Foundry it will need to look up its PostgreSQL connect details. For this we use the cf-env package by Patrick Mueller.

Create a config.js file that includes local development and Cloud Foundry production:

var path = require('path'),
    config, mysql;
var env = process.env.NODE_ENV || 'development';
var production = env == 'production';
var cfCore = {};
var sqlCredentials = {};
var mailCredentials = {};
if (production) {
  var cfEnv = require("cf-env");
  var pkg   = require("./package.json");
  var cfCore = cfEnv.getCore({name: pkg.name});
  var sqlCredentials = cfEnv.getService("ghost-pg").credentials;
  var mailCredentials = cfEnv.getService("ghost-mail").credentials;
}
console.log(sqlCredentials);
console.log(mailCredentials);
config = {
    development: {
      url: 'http://my-ghost-blog.com',
      database: {
        client: 'sqlite3',
        connection: {
            filename: path.join(__dirname, '/content/data/ghost-dev.db')
        },
        debug: false
      },
      server: {
        host: '127.0.0.1',
        port: '2368'
      },
      paths: {
        contentPath: path.join(__dirname, '/content/')
      }
    },
    // Cloud Foundry
    production: {
      url: cfCore.url,
      mail: {
        transport: 'SMTP',
        options: {
          service: 'Sendgrid',
          auth: {
            user: mailCredentials.username,
            pass: mailCredentials.password,
          }
        }
      },
      database: {
        client: 'pg',
        connection: sqlCredentials.uri,
        pool: {
          min: 2,
          max: 4
        },
        debug: false
      },
      server: {
        host: cfCore.bind,
        port: cfCore.port
      },
      logging: false
    },
};
module.exports = config;

There is more information on general Ghost configuration.

Create a Procfile to tell Cloud Foundry what command to run to launch Ghost:

web: NODE_ENV=production npm start

By setting $NODE_ENV to production we will load the Postgres service credentials within Cloud Foundry; rather than assume there is a local Sqlite3 database.

Create a .cfignore file so that 15Mb of Node.js modules are not unnecessarily uploaded each time you deploy:

node_modules

Animated GIF of deployment

deploy

Postgres service

The running Ghost application requires a SQL service. Above we configured it for Postgresql, assuming that you would pick a free, small database initially.

On Pivotal Web Services, there is ElephantSQL for Postgres and SendGrid for Email.

pivotal-marketplace

$ cf m
Getting services from marketplace in org drnic-starkandwayne / space development as [email protected]
OK
service          plans                                                                 description
...
elephantsql      turtle, panda, hippo, elephant                                        PostgreSQL as a Service

The turtle plan is free for 20Mb and 4 simultaneous DB connections (see config.js above for how connections were constrained).

$ cf cs elephantsql turtle ghost-pg

Note, ghost-pg is the name you are giving your service. It should match to the cfEnv.getService("ghost-pg"); name in config.js.

Email service

On Pivotal there is a free SendGrid service. As per the config.js, name it ghost-mail.

$ cf cs sendgrid free ghost-mail

Deploy app and bind service

Providing the Postgres and Mail services to the Node.js application is called "binding".

As of May 2014 there is a chicken-and-egg puzzle for binding a service to an application that requires the service.

The Ghost application needs the Postgres service to be bound to it before the Ghost application can successfully run. But the service needs the application to have been created in Cloud Foundry before it can be bound.

To solution is the following steps:

  • Push the application but do not start it running (--no-start flag)
  • Bind the services to the application
  • Start the application (always need to restart an application that has new or changed service bindings)
cf push my-ghost-blog -m 256M --no-start
cf bs my-ghost-blog ghost-pg
cf bs my-ghost-blog ghost-mail
cf restart my-ghost-blog

Above we are naming the application my-ghost-blog on our Cloud Foundry account, allocating it 256M of RAM (the default is 1G which will cost 4x more per month), and telling Cloud Foundry to hold off from actually starting the app.

Then we bind the services (shortened to bs) to the application.

Finally, (re-)start the application.

Connection limits

If your database has connection limits (as ElephantSQL does) then Ghost may fail several times during initial start up. It may startup but secret will fail to setup some initial databsae configuration. This took us a few hours to debug.

The following section of your config.js is important:

pool: {
  min: 2,
  max: 4
},

I’ve emailed ElephantSQL to ask them to pass through the number of available concurrent connections (for the chosen service plan). Currently I had to look up that Tiny Turtle has limit of 4 concurrent connections.

plans

Spread the word

twitter icon facebook icon linkedin icon