Node.js / GHOST theme development and deployment

Few weeks ago we decided to switch our static site to GHOST and write a theme for it. Now, GHOST isn’t an optimal choice for a photography portfolio and we’ll get into why that’s the case in a bit, but we both like blogging to, so we wanted to give it a shot.

Cheap Servers for GHOST / Node.js Hosting

In order to host our blog and fresh portfolio we decided to go for either Digital Ocean or Linode since we could easily create a virtual private server and the 5$ a month plan should suffice for our traffic estimates for some time.

In order to follow the steps for this tutorial you should have a look at my previous post: How to use SSH keys for Authentication (for beginners)

If you’re unfamiliar with gulp, go read a quick tutorial somewhere, you won’t need it for the basics, but if your theme needs concatenated JavaScript files or compiled SASS files, it may be useful. We’re using it, but it’s no must have.

Deployment: git push, PM2 and pod

After some looking around it seemed that pm2/pod would be a pragmatic choice for deploying new versions of our theme with a git push to the right remote. I wanted the deployment process to be a swift as possible, so we wouldn’t be afraid to launch early, because we quickly could fix bugs in production.


The catch with GHOST is that you need to restart the node/express instance that GHOST starts, because in production mode, GHOST doesn’t really care which files you change in your themes directory.

If you haven’t tried it, try pod for deploying a node app, it’s a lot of fun and very easy if you use one server for multiple applications. When pushing to a repository, pod will use pm2, which it is built around to restart your process.

In a .podhook file in our repository we saved 3 lines of text that install frontend dependencies, run the gulp-build task and lastly restart GHOST. More specifically it restarts the named pm2 process that is reserved for that specific GHOST instance.

npm install
gulp build
pm2 restart gegenwind-ghost

Sadly the pod project seems to have gone stale on github, but it’s still a cool tool and a little more convenient than a post-receive hook.

Here’s some example output of how it looks when we deploy:

(master) % git push deploy master
Counting objects: 43, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (32/32), done.
Writing objects: 100% (32/32), 3.47 KiB | 0 bytes/s, done.
Total 32 (delta 26), reused 0 (delta 0)
remote: Fetching origin
remote: From /home/gegenwind/pods/repos/ghost-get-shot
remote: de0ee7c..51fc53d master -> origin/master
remote: HEAD is now at 51fc53d commit message'
remote: npm WARN package.json GetShot@ No repository field.
remote: npm WARN package.json GetShot@ No license field.
remote: [08:45:21] Using gulpfile ~/pods/apps/ghost-get-shot/gulpfile.js
remote: [08:45:21] Starting 'copyJS'...
remote: [08:45:21] Finished 'copyJS' after 23 ms
remote: [08:45:21] Starting 'styles'...
remote: [08:45:21] Finished 'styles' after 75 ms
remote: [08:45:21] Starting 'build'...
remote: [08:45:21] Finished 'build' after 13 μs
remote: [PM2] restartProcessId process id 2
remote: ┌─────────────────┬────┬─────────┬─────┬────────┬─────────┬────────┬─────────────┬──────────┐
remote: │ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │
remote: ├─────────────────┼────┼─────────┼─────┼────────┼─────────┼────────┼─────────────┼──────────┤
remote: │ gegenwind-ghost │ 2 │ cluster │ 867 │ online │ 40 │ 0s │ 23.684 MB │ disabled │
remote: └─────────────────┴────┴─────────┴─────┴────────┴─────────┴────────┴─────────────┴──────────┘
remote: Use `pm2 show <id|name>` to get more details about an app

Pod might complain that there is nothing to start inside your theme directory, but as long as pm2 restarts GHOST, you’re all good.

Theme Development and Deployment Workflow with GHOST

Writing a GHOST theme basically means you write frontend code, Handlebars templates, CSS or a pre-processor of your choice (in our case that was SASS).


Our workflow:

  1. develop feature on local machine
  2. commit changes to repository
  3. git push origin master && git push deploy master

Overall it was a great experience, our theme now has around 1000 lines of template and style without the amp template. The images are displayed as big as they can be without going off screen into any dimension and the navigation elements are reasonably sized on mobile.

Git allowed us to have a high frequency of iterations and to just yell into the other room: I’ve pushed, check it out!

Drawbacks of GHOST

GHOST isn’t optimal for photography portfolios in relation to page speed because the media management does not support multiple image sizes, which means you have to think about which resolution to upload, GHOST will not resize them for you.

Also the templates so far, until the future channels are released, are a little inflexible and you have to trick around a bit to not have your blog posts be in the front page of your site. That will also cause us some refactoring in the future, but hey, it’s still writing handlebars and SCSS, so I won’t complain 😉


Summary: git deploy and be happy

The worst part about this whole setup is that there is about a second of downtime since we only have one worker process without a cache and without a load balancer in front of it. If we had to deploy multiple times per day or if we were running a bank, that wouldn’t be good enough, apart from that it’s great!


Once configured you can update the theme with a simple command and you don’t have to ssh into your server or use some ftp client to upload new files, which is absolutely fantastic and it allowed us to work very closely and quickly.

Commit statistics for the repository branch master Sep 17 – Oct 23:

  • 81 commits during 36 days
  • Average 2 commits per day
  • Contributed by 2 authors

If you want to check out the current status of the site, have a look at!

2 thoughts on “Node.js / GHOST theme development and deployment”

Leave a Reply

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