Installing and Upgrading Ghost Without the CLI

February 26, 2018 update: There is a more recent article here.

Ghost was released in version 1.0.0 a few weeks ago. As we described in an earlier article, the supported way of installing and updating Ghost from version 1 onwards is by using the Ghost CLI tool. Unfortunately, the Ghost CLI requires sudo privileges to work, and it makes some very specific assumptions about the server setup that means it is really only suitable for installations on a virtual server that is dedicated to running Ghost.

Setting up and maintaining a VPS specifically for Ghost is not necessarily a big deal. The cost of a small VPS that can run Ghost is perhaps $5-$10 per month, which is less expensive than the hosted option provided by, and the installation instructions are quite comprehensive, starting from a newly provisioned Ubuntu 16.04 VPS. However, in our case, the resulting setup differs significantly from how we run other node.js applications, and we really don't want to have to deal with a different setup for Ghost. Others have been running their Ghost blogs on shared hosting, but the new CLI-based installation and maintenance won't work in that environment.

Not surprisingly some people have come up with alternative installation procedures. For example, this blog post describes a manual installation procedure that works quite well. The article doesn't describe how upgrades are done, but we tried a manual upgrade from Ghost 1.5.1 to Ghost 1.5.2 by adapting the release 0.x upgrade procedure and that worked fine. However, there is an alternative method that we have been testing, and that we think will make Ghost maintenance much easier than the manual update procedure.

Installing Ghost as an NPM Module

It is possible to install and run Ghost as an NPM module (it is not mentioned in the installation instructions, but the procedure is described as an "advanced" topic in the official Ghost documentation). We tried this on a shared server with node 6.11.2 installed using nvm and with yarn globally available. These are the steps, starting from the install directory:

Ghost uses knex-migrator for database setup and updating, so we need that installed:

yarn global add knex-migrator

We also need a package.json file in the install directory, so go ahead and create it:

yarn init

(You can just accept the defaults when you are prompted for name, version, etc).

Now go ahead and install Ghost:

yarn add ghost@latest --save

Once the installation completes we need a configuration file in the install directory. Since we are installing Ghost for production we will use config.production.json:

cp node_modules/ghost/core/server/config/env/config.production.json config.production.json .

You will need to add the URL of your blog to this file and provide server port number and database connection information (defaults to MySQL - see the config documentation if you need to use SQLite). This is what our config file contains:

    "url": "",
    "server": {
        "host": "",
        "port": 4010
    "database": {
        "client": "mysql",
        "connection": {
            "host"     : "",
            "user"     : "dbca844527ff",
            "password" : "1234567890",
            "database" : "ghost"
    "auth": {
        "type": "password"
    "paths": {
        "contentPath": "content/"
    "logging": {
        "level": "info",
        "rotation": {
            "enabled": true
        "transports": ["file", "stdout"]

We also need an index.js file:

var ghost = require('ghost');
var path = require('path');

ghost().then(function (ghostServer) {

And finally we need to create a content folder with the appropriate subfolders. We'll copy the node_modules/content folder to our installation directory:

cp -r node_modules/ghost/content .

At this point our installation folder should contain the following files and folders:

$ ls -1

The last step we need to perform before starting Ghost is database initialisation:

NODE_ENV=production knex-migrator init --mgpath node_modules/ghost

You should see output from this command similar to:

[2017-08-12 17:15:37] INFO Creating table: posts 
[2017-08-12 17:15:37] INFO Creating table: users 
[2017-08-12 17:15:37] INFO Creating table: roles 
[2017-08-12 17:15:37] INFO Creating table: roles_users 
[2017-08-12 17:15:37] INFO Creating table: permissions 
[2017-08-12 17:15:37] INFO Creating table: permissions_users 
[2017-08-12 17:15:37] INFO Creating table: permissions_roles 
[2017-08-12 17:15:37] INFO Creating table: permissions_apps 
[2017-08-12 17:15:37] INFO Creating table: settings 
[2017-08-12 17:15:37] INFO Creating table: tags 
[2017-08-12 17:15:37] INFO Creating table: posts_tags 
[2017-08-12 17:15:37] INFO Creating table: apps 
[2017-08-12 17:15:37] INFO Creating table: app_settings 
[2017-08-12 17:15:37] INFO Creating table: app_fields 
[2017-08-12 17:15:37] INFO Creating table: clients 
[2017-08-12 17:15:37] INFO Creating table: client_trusted_domains 
[2017-08-12 17:15:37] INFO Creating table: accesstokens 
[2017-08-12 17:15:38] INFO Creating table: refreshtokens 
[2017-08-12 17:15:38] INFO Creating table: subscribers 
[2017-08-12 17:15:38] INFO Creating table: invites 
[2017-08-12 17:15:38] INFO Creating table: brute 
[2017-08-12 17:15:38] INFO Model: Post 
[2017-08-12 17:15:38] INFO Model: Tag 
[2017-08-12 17:15:38] INFO Model: Client 
[2017-08-12 17:15:38] INFO Model: Role 
[2017-08-12 17:15:38] INFO Model: Permission 
[2017-08-12 17:15:38] INFO Model: User 
[2017-08-12 17:15:39] INFO Relation: Role to Permission 
[2017-08-12 17:15:39] INFO Relation: Post to Tag 
[2017-08-12 17:15:39] INFO Relation: User to Role 
[2017-08-12 19:15:39] INFO Finished database init! 

We can now start Ghost from the command line using

NODE_ENV=production node index.js

Once you have verified that Ghost is working as expected you can then persist it using systemd or similar.

Updating Ghost from the Command Line

If Ghost was installed as described above this is what you would do to update to the most recent 1.x release:

run yarn outdated to see what updates are available. E.g:

$ yarn outdated
yarn outdated v0.27.5
Package Current Wanted Latest Package Type URL             
ghost   1.4.0   1.4.0  1.5.2  dependencies
Done in 0.38s.

In this case we are running version 1.4.0, but version 1.5.2 is available. However, because ghost was added to package.json with a specific version number, yarn will not allow an update. We can fix that in several ways, e.g. by prepending the version number in package.json with a caret:

  "dependencies": {
    "ghost": "^1.4.0"

This will allow yarn to update ghost to any 1.x.x release.

Before proceeding you should ensure that you have a current backup, and you should read through the relevant release notes to verify that any custom theme code is support by the version of Ghost that you are upgrading to.

To update to the most recent Ghost release run:

yarn upgrade

And perform a database migration:

NODE_ENV=production knex-migrator migrate --mgpath node_modules/ghost

If you are using the default "Casper" theme you should also copy the new version from the node_modules/ghost/content/themes folder to the content/themes folder in your install directory:

rm -r content/themes/casper
cp -r node_modules/ghost/content/themes/casper content/themes

Restart Ghost and open your blog in the browser to verify that everything is working as expected and that you are running the most recent Ghost release.

Wrapping Up

At the time of writing this blog installation is still running Ghost version 0.11.10, mainly because our theme contains some helper methods that need to be updated to work with the 1.x release. But based on our tests we are confident that we can run Ghost 1.x and keep it updated in our own environment and with a minimum of effort using the procedure described above. Anyone who has been running Ghost 0.11 on their own system or on shared hosting should be able to do the same.

This is much easier than the official, manual update method we have been using until now with version version 0.x. And even if the Ghost CLI was a bit less opinionated, we might prefer handling installations and updates this way since it lets us work with the same tools that we use for any other node.js project - one less tool to learn.