How-tos

Deploying Ghost.js on IBM Bluemix

Share this post:

Ghost is a crowd funded Open Source Node.js application which allows you to write and publish your own blog, giving you the tools to make it easy and even fun to do. It’s simple, elegant, and designed so that you can spend less time making your blog work and more time blogging.

In order to have Ghost.js work on the IBM BlueMix Platform, do the following:

  1. Download a current stable version of Ghost from https://ghost.org/download/
  2. These steps have been validated to work with Ghost 0.6.4
  3. Unzip the download (e.g. unzip Ghost-0.4.1.zip) and change to the base directory (e.g. ghost-0.4.1)
  4. Clone a copy of this git repository and copy the content within the files folder into the root of your ghost installation directory.  You will replace one (1) file (package.json), delete one (1) file (npm-shrinkwrap.json) and add three (3) new files (config.js,  manifest.yml and manifest.cfg [exact copy of manifest.yml]) to the default Ghost.js installation to enable its use with the IBM BlueMix platform.
    • manifest.yml : A BlueMix application manifest. An application manifest defines a set of application deployment settings, such as the name of an application, the number of instances to deploy, the maximum memory available to an instance, the services it uses, and so on. [Source]. While not mandatory, using an application manifest simplifies entry of deployment settings within a single file.
      ---
      applications:
      - name: REPLACE_ME
        memory: 512M
        instances: 1
        host: REPLACE_ME
        domain: mybluemix.net
        path: .
        command: node index --production
        env:
          NODE_ENV: production
        services:
        - ghostdbservice
    • manifest.cfg : A file that is an exact copy of the manifest.yml.  During application deploys, the manifest.yml is NOT included within the deployed applications file structure.  This makes sense – since this file typically contains deploy configuration information.  However, to make these instructions simpler – I’ve chosen to leverage the deployment config info to help the config.js code magically “compute” your Ghost.js Bluemix url.  To facilitate this computation, we need to make an exact copy of our final manifest.yml.
    • config.js : This file allows you to set environment level configuration for things like your URL, database, and mail settings. [Source] For the BlueMix platform, database connection information is dynamically generated during service instantiation and shared within environment variables. Therefore, a config file structured to read the database environment variable details is necessary.
      // # Ghost Configuration
      // Setup your Ghost install for various environments
      
      var path = require('path'),
          config;
      
      // Modifications for BlueMix compatibility
      var postCreds;
      var bluemixport = (process.env.VCAP_APP_PORT || '2368');
      var bluemixhost = (process.env.VCAP_APP_HOST || '127.0.0.1');
      var yaml = require('js-yaml');
      var fs = require('fs');
      var apphost = '';
      var appdomain = '';
      var appurl = '';
      
      // Read Manifest.yml file to construct ghost application url or throw exception on error
      try {
        var doc = yaml.safeLoad(fs.readFileSync('./manifest.cfg', 'utf8'));
        apphost = doc.applications[0].host;
        appdomain = doc.applications[0].domain;
        appurl = 'http://' + apphost + '.' + appdomain;
      } catch (e) {
        console.log(e);
      }
      
      if (process.env.VCAP_SERVICES) {
          var services = JSON.parse(process.env.VCAP_SERVICES);
          // look for a service starting with 'mysql'
          // MySQL is the only one supported by Ghost right now
          for (var svcName in services) {
              if (svcName.match(/^mysql/)) {
                  postCreds = services[svcName][0]['credentials'];
                  postCreds.client = 'mysql';
                  postCreds.filename = '';
              }
          }
      } else {
          // Let's assume we're running locally and populate
          postCreds = {
              name : '',
              host : '127.0.0.1',
              port : '2368',
              user : '',
              password : '',
              client : 'mysql',
              filename : path.join(__dirname, '/content/data/ghost-dev.db')
          };
      }
      
      console.log(JSON.stringify(postCreds));
      
      config = {
          // ### Development **(default)**
          development: {
              // The url to use when providing links to the site, E.g. in RSS and email.
              url: 'http://my_app_name.mybluemix.net',
      
              // Example mail config
              // Visit http://docs.ghost.org/mail for instructions
              // ```
              //  mail: {
              //      transport: 'SMTP',
              //      options: {
              //          service: 'Mailgun',
              //          auth: {
              //              user: '', // mailgun username
              //              pass: ''  // mailgun password
              //          }
              //      }
              //  },
              // ```
      
              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'
              }
          },
      
          // ### Production
          // When running Ghost in the wild, use the production environment
          // Configure your URL and mail settings here
          production: {
              // URL constructed from data within the manifest.yml file.
              url: appurl,
              
              // Example mail config
              // Visit http://docs.ghost.org/mail for instructions
              // ```
              //  mail: {
              //      transport: 'SMTP',
              //      options: {
              //          service: 'Mailgun',
              //          auth: {
              //              user: '', // mailgun username
              //              pass: ''  // mailgun password
              //          }
              //      }
              //  },
              // ```
              
              database: {
                  client: postCreds.client,
                  connection: {
                     filename: postCreds.filename,
                     host: postCreds.hostname,
                     user: postCreds.username,
                     password: postCreds.password,
                     database: postCreds.name,
                     port: postCreds.port,
                     charset: 'utf8'
                  },
                  debug: false
              },
              server: {
                  // Host to be passed to node's `net.Server#listen()`
                  host: bluemixhost,
                  // Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
                  port: bluemixport
              }
          }
      };
      
      // Export config
      module.exports = config;
      
    • package.json : All npm packages contain a file, usually in the project root, called package.json – this file holds various metadata relevant to the project. This file is used to give information to npm that allows it to identify the project as well as handle the project’s dependencies. [Source]
  5. Update the manifest.yml fields for name and host with an app name of your choice.
  6. Make a copy of the manifest.yml and name it: manifest.cfg in the same directory
  7. Using the cloud foundry v6 cli, login to BlueMix, create a back-end database service named ghostdbservice and install Ghost.js.
    #Step 0: cf login -a 
    $cf login -a https://api.ng.bluemix.net
    
    # Step 1: cf create-service   
    $ cf create-service mysql 100 ghostdbservice
    
    # Step 2: CF_STAGING_TIMEOUT=4 CF_STARTUP_TIMEOUT=4 cf push -s cflinuxfs2
    # CF_STAGING_TIME : Max wait time for app instance startup, in minutes
    # CF_STARTUP_TIMEOUT : Max wait time for app instance startup, in minutes<br /># -s : Argument to explicitly ask for the cflinuxfs2 stack (Trusty Ubuntu 14.04)
    # ENV variables are suggested to accommodate longer staging and startup times.
    $ CF_STAGING_TIMEOUT=4 CF_STARTUP_TIMEOUT=4 cf push -s cflinuxfs2
    
    
  8. After the application is running on BlueMix, browse to http://<your_app_name>.mybluemix.net/ghost/ and complete the sign-up registration steps.
  9. Some possible next steps after successfully deploying include: downloading additional themes for your blog site, setting up an SMTP transport within the config.js file, hacking the ghost.js code, …  Ghost.js rocks!
  10. I would also recommend checking out my follow-up post on how to enhance your Ghost deploy to include Cloudant as a connected image store so that your uploaded images do not disappear from your blog.
  11. That’s it!  Would love to hear how this works for you.  Happy Blogging!

Caveats

  • Debugging failed deploys can be tricky.  Some potholes to try and avoid:
    • Be careful adding packages to the package.json that require native compilation.  For example, SQLITE3 caused me headaches during deployment until I removed it.  The error was related to a missing OS function on Linux 3.0.0 related to Python’s shared memory implementation with SemLock.
    • Typos in config.js can be fatal.  For example, I missed a comma and the config could not be parsed during the application’s startup.  Make single changes and always keep a working backup.
    • I noticed that if I used a different terminal to tail the application’s logs, my application push uploads would timeout and fail.  I didn’t dig deeper, but had more consistent application push success when no log listening was attached to the app.

Add Comment
13 Comments

Leave a Reply

Your email address will not be published.Required fields are marked *


Millard

Awesome! Huge props for working this out and sharing it.

Reply

theMcQ

I managed to get this going but it took a bit of a dance.

For reasons that aren’t clear to me, BlueMix wasn’t setting the NODE_ENV variable properly based on what was in the manifest. I “fixed” this by just hammering it in the main index.js file. Then it appeared to be failing because of a missing dependency on “js-yaml”, which I fixed by adding one to the package.json.

At that point, it started to run, but but there are still problems. It seems to randomly fail to serve up the “blog cover” and “blog logo” images, and sometimes just displays an entirely blank page. I can’t tell whether this is bugs in Ghost, something that is failing because of an incorrect dependency (or similar), or just a side-effect of the ephemeral nature of the filesystem on BlueMix.

Reply

    spj

    hmmm …. did not run into the NODE_ENV behavior during my deploys. With regard to the missing dependency, did you clone or download the files from the linked repository? I double-checked the package.json within the repo and it does contain the js-yaml declaration. If you kept the original package.json, you could try replacing it with the one from the repo.
    WRT to the ephemeral nature of file images, I would recommend taking a look at the config setting fileStorage. You can set that value to false per guidance here (http://docs.ghost.org/usage/configuration/) for PaaS environments. This would eliminate the option of uploading local files and require urls only.

    Reply

theMcQ

I downloaded the latest zip of Ghost, then cloned the repo containing your files and then copied them over the ones from the zip. I’m not sure how this ended up in a different state. Oh well.

re “ephemeral” — I grok that fileStorage false would “fix” this — when I looked at the URL for the missing background image it was “…/content/images/2014/Mar/image.jpg/” so it was pretty clear what was going on. The problem is, a blogging platform that has no way to hold the images it uses is pretty useless… I guess you could do some kind split posting thing using Ghost + DropBox (or whatever), but it’s pretty inconvenient by comparison.

Reply

    spj

    @theMcQ . Took your comments to heart. Since BlueMix makes instantiating services soooooo easy, I tinkered with Cloudant and came up with a simple tweak that I think gives the best of both worlds. Read more @ https://developer.ibm.com/bluemix/2014/04/24/enhancing-ghost-js-cloudant-ibm-codename-bluemix/ . Basically, Ghost.js continues to allow local file uploads, but it then post-processes it into a Cloudant resource URL which is subsequently leveraged by Ghost.js.

    Reply

Rafael

First of all, ty for this article, but I’m facing some probles to start the app:

2014-08-28T10:57:12.32-0300 [App/0] ERR ERROR: Your site url in config.js is invalid.
2014-08-28T10:57:12.32-0300 [App/0] ERR Please make sure this is a valid url before restarting
2014-08-28T10:57:12.32-0300 [App/0] ERR Error: Your site url in config.js is invalid.
2014-08-28T10:57:12.32-0300 [App/0] ERR at validateConfigEnvironment (/home/vcap/app/core/bootstrap.js:86:25)
2014-08-28T10:57:12.32-0300 [App/0] ERR at NearFulfilledProxy.when (/home/vcap/app/node_modules/when/when.js:501:43)
2014-08-28T10:57:12.32-0300 [App/0] ERR at Promise._message (/home/vcap/app/node_modules/when/when.js:426:25)
2014-08-28T10:57:12.32-0300 [App/0] ERR at Array.deliver [as 0] (/home/vcap/app/node_modules/when/when.js:319:7)
2014-08-28T10:57:12.32-0300 [App/0] ERR at runHandlers (/home/vcap/app/node_modules/when/when.js:385:12)
2014-08-28T10:57:12.32-0300 [App/0] ERR at Array.2 (/home/vcap/app/node_modules/when/when.js:352:5)
2014-08-28T10:57:12.32-0300 [App/0] ERR at runHandlers (/home/vcap/app/node_modules/when/when.js:385:12)
2014-08-28T10:57:12.32-0300 [App/0] ERR at drainQueue (/home/vcap/app/node_modules/when/when.js:836:3)
2014-08-28T10:57:12.32-0300 [App/0] ERR at process._tickDomainCallback (node.js:463:13)
2014-08-28T10:57:12.32-0300 [App/0] ERR
2014-08-28T10:57:12.32-0300 [App/0] ERR ERROR: invalid site url
2014-08-28T10:57:12.32-0300 [App/0] ERR Error: invalid site url
2014-08-28T10:57:12.32-0300 [App/0] ERR at validateConfigEnvironment (/home/vcap/app/core/bootstrap.js:88:28)
2014-08-28T10:57:12.32-0300 [App/0] ERR at NearFulfilledProxy.when (/home/vcap/app/node_modules/when/when.js:501:43)
2014-08-28T10:57:12.32-0300 [App/0] ERR at Promise._message (/home/vcap/app/node_modules/when/when.js:426:25)
2014-08-28T10:57:12.32-0300 [App/0] ERR at Array.deliver [as 0] (/home/vcap/app/node_modules/when/when.js:319:7)
2014-08-28T10:57:12.32-0300 [App/0] ERR at runHandlers (/home/vcap/app/node_modules/when/when.js:385:12)
2014-08-28T10:57:12.32-0300 [App/0] ERR at Array.2 (/home/vcap/app/node_modules/when/when.js:352:5)
2014-08-28T10:57:12.32-0300 [App/0] ERR at runHandlers (/home/vcap/app/node_modules/when/when.js:385:12)
2014-08-28T10:57:12.32-0300 [App/0] ERR at drainQueue (/home/vcap/app/node_modules/when/when.js:836:3)
2014-08-28T10:57:12.32-0300 [App/0] ERR at process._tickDomainCallback (node.js:463:13)
2014-08-28T10:57:12.39-0300 [DEA] OUT Instance (index 0) failed to start accepting connections
2014-08-28T10:57:12.40-0300 [API] OUT App instance exited with guid 7b5347ec-3421-4e73-8658-a3e6287e8c90 payload: {“cc_partition”=>”default”, “droplet”=>”7b5347ec-3421-4e73-8658-a3e6287e8c90”, “version”=>”b1bfea09-989a-4ab8-b19c-e6a3201b3fa8”, “instance”=>”405114bab95f44a89e81d7c1684c7c14”, “index”=>0, “reason”=>”CRASHED”, “exit_status”=>0, “exit_description”=>”app instance exited”, “crash_timestamp”=>1409234232}

Here is my manifest:

applications:
– name: zago-blog
memory: 512M
instances: 1
host: zago-blog
domain: mybluemix.net
path: .
command: node index –production
env:
NODE_ENV: production
services:
– ghostdbservice

I needed to change the domain because I’m not able to push the application:
Creating route zago-blog.ng.bluemix.net…
FAILED
Server error, status code: 400, error code: 210001, message: The route is invalid: domain invalid_relation

Reply

    Sanjay.Joshi

    Great catch on the post. I will update the post to reflect the use of mybluemix.net as the correct domain now. The ng.bluemix.net domain is a reserved domain and cannot be used for user apps. mybluemix.net is the correct domain.

    With regards to your app startup problems, try adding a console.log statement after this https://github.com/ibmjstart/bluemix-ghost-js/blob/master/files/config.js#L22 to see what appurl is being constructed. For your example, it should print out http://zago-blog.mybluemix.net . Alternately, I’ve recently become aware of a change that causes the manifest.yml to be ignored during the push. So, you could also try copying the manifest.yml file and saving it as manifest.yml.cfg . Then modify https://github.com/ibmjstart/bluemix-ghost-js/blob/master/files/config.js#L19 accordingly.

    Finally, if that gets you going … I would encourage you to read my follow-up post for setting up Cloudant with your ghost.js blog (link) to work around the limitations of ephemeral storage present in Platform as a Service solutions. This would ensure that uploaded images persist in your blog at all times.

    Good luck and let me know how it goes.

    Reply

      Guy

      Yes for sure the manifest.yml got ignored. I got a enoent error message telling me that file could not be found, so i copied it per you instructions and changed it in the config. I all works now.
      Finally , ghost is already at several levels higher, are you going to push an update for a more recent release as several things have changed.
      thanks

      Reply

matusek

Hi, thanks for guide.. but when I made a push of my applications some node_models from package.json aren’t installed which led to
Build succeeded!
├── UNMET DEPENDENCY ansi-to-html@^0.3.0
├── bcryptjs@2.1.0
├── bluebird@2.9.25
├── body-parser@1.10.0
├── bookshelf@0.7.9
├── busboy@0.2.9
├── chalk@1.0.0
├── cheerio@0.18.0
├── compression@1.2.2
├── connect-slashes@1.3.0
├── cookie-session@1.1.0
├── downsize@0.0.8
├── express@4.12.3
├── express-hbs@0.8.4
├── extract-zip@1.0.3
├── fs-extra@0.13.0
├── glob@4.3.2
├── html-to-text@1.3.0
├── istanbul@0.3.14 extraneous
├── UNMET DEPENDENCY js-yaml@^3.3.1
├── knex@0.7.3
├── lodash@2.4.1
├── mocha@2.2.5 extraneous
├── moment@2.10.2
├── morgan@1.5.0
├── mysql@2.1.1
├── UNMET DEPENDENCY nano@^6.1.4
├── UNMET DEPENDENCY node-polyglot@^0.4.3
├── node-uuid@1.4.2
├── nodemailer@0.7.1
├── oauth2orize@1.0.1
├── passport@0.2.1
├── passport-http-bearer@1.0.1
├── passport-oauth2-client-password@0.1.2
├── path-match@1.2.2
├── pg@4.1.1
├── request@2.55.0
├── rss@1.1.1
├── semver@4.3.3
├── UNMET DEPENDENCY showdown@^1.2.1
├── showdown-ghost@0.3.6
├── UNMET DEPENDENCY socket.io@^1.3.6
├── sqlite3@3.0.8
├── UNMET DEPENDENCY underscore@^1.8.3
├── unidecode@0.1.3
├── validator@3.39.0
├── UNMET DEPENDENCY when@^3.7.3
└── xml@1.0.0

is there any way how to make npm update on bluemix server?
Thank you..

Reply

    Sanjay.Joshi

    Hi,
    Thanks for the comment and letting us know about npm error. I’ve updated the blog post to deal with changes in the Ghost file structure since it was last written. In a nutshell, 2 big things were updated:
    1) The npm-shrinkwrap.json is causing the UNMET error. Simply delete that file from your ghost root folder. This file is useful to lockdown dependencies, however we are adding some additional Node modules and therefore do not want this file to block the installer from adding our extra modules.
    2) The manifest.yml is no longer included in the deployed application file structure. So, I’ve added instructions to make an additional exact copy of it and name it manifest.cfg . This is then inspected by the config.js file to “compute” the hosted blogsite’s app URL. Of course, you’re free to hardcode the value and skip making a copy altogether.
    3) Finally, I’d recommend exploring the Step 10 follow-up post. In that post, I articulate how to add a Cloudant ImageStore to your Ghost deploy so that your blog site doesn’t start encountering missing images that you might have uploaded onto the site after pushing the original app.
    Good luck and look forward to hearing how it goes. After these 2 changes, I was able to deploy onto Bluemix without error.

    Reply

      matusek

      Thank you Sanjay.Joshi app is deployed also with cloudant 😀
      I would like to understand why are you using cloudantDB for saving static content? I was thinking that for that purpose is Object Storage service..
      And I assume that MySQL is there just because of native ghost support?
      Because now I am planing to build another custom app and I was thinking that for data we will use cloudantDB and for images and attachments we will use Object Storage v2.. So now am little bit confused..

      Reply

        Sanjay.Joshi

        Awesome. Its a nice feeling to see it all working together. You’ve asked some great questions – which I’ll try my best to answer. This blog post and the subsequent Cloudant enhancement were written in early 2014 – well ahead of the Object Storage service and Bluemix General Availability 🙂 I really wanted to figure out how to have Ghost run in an effective way on a cloud platform without relying on local file storage. Cloudant was my only option at the time. Happily, Cloudant is an excellent storage service for lots of small attachments as long as the sizes are relatively small – so I’ve not felt compelled to transition the approach to Object Storage. If you’d like to contribute an additional storage *.js variant that leverages the Object Storage service, we always welcome Pull Requests to the repo. You are correct regarding the MySQL support being native to ghost. They support DBs that are facilitated by Bookshelf.js. For your new app, I think using Cloudant as your main backend and Object storage for images/attachments makes alot of sense. Lots of different paths to the same destination 🙂 Hope this helped in some small way with context.

        Reply






















































































































































































































































More How-tos Stories

Securely Access MongoDB from a Spring Boot App Running on Bluemix Kubernetes Using Kubernetes Secrets

This example builds on my previous post where I showed how to access a Bluemix MongoDB service from a Spring data app running locally. In that simple example the MongoDB credentials were either hard coded in the application or specified manually on the command line.

Continue reading

Accessing a Bluemix MongoDB Service from a Java Spring Boot Application

In this blog post I'll show how to access a Compose for MongoDB database running on IBM Bluemix from a Spring boot application running locally. Spring is a popular open source framework and container for Java applications. MongoDB is a popular open source document oriented NoSQL database that uses JSON-like documents.

Continue reading

Spring, Liberty and Single Sign On

Have you ever wondered how you could protect your Spring app with the Bluemix SSO service? In this article, we’ll cover how you can convert a Spring application running on Liberty from using a manually configured Open ID Connect (OIDC) Server, to using the Bluemix Single Sign On service. A short while ago I wrote […]

Continue reading