4 minute read

I just finished migrating this blog from WordPress to Ghost.


WordPress served me well for a long time, but it led to a setup that was hard to maintain. In order to get fairly basic functionality, I used a complex custom theme and a handful of plugins that all needed to be kept up to date.

It was also becoming increasingly annoying to write posts in the WordPress editor, especially as nearly every other tool I used supported Markdown.

It finally came to a head when the theme was removed and I had to do something: Suffusion Not Available Any More. At that point, it seemed to be about as much work to find and configure a new theme (and upgrade all of my plugins) as it would be to find a new blogging tool.

I looked around and Ghost seemed good. I knew others who were very happy with it, and there was a clear migration path from WordPress. It also seemed full featured and fast enough that I wouldn’t have to install a lot of plugins (like I did with WordPress).

How I migrated

The migration wasn’t too painful, but it was involved. Hopefully, I can help others out in the future by documenting my steps.

Running Ghost

The first thing I needed was a way to run Ghost. I didn’t want to worry about installing all of Ghost’s dependencies, so I decided to run it via Docker. This way, I can also upgrade by just changing the version of the Docker image. I set up an init script that basically just does this:

  /usr/bin/docker run \
    -e NODE_ENV=production \
    -p \
    -v /var/www/ghost:/var/lib/ghost \
    --log-driver=syslog \
    --rm \

This runs ghost on and mounts /var/www/ghost into the container. This way, I can keep my files across Docker containers. Then, I fronted this with Apache.

Configuring Ghost

  • I decided to stick with the default theme (Casper) for now. Then, I went through all of the settings and configured the obvious ones (title, description, etc). And I added Google Analytics to the footer.

  • I turned on Dated Permalinks. Conveniently, this setting makes all of the URLs the same as my WordPress setup so I don’t have to add a bunch of redirects.

  • I wanted syntax highlighting, but most of the info I found online involved editing the Ghost theme (or switching to a custom theme). I really didn’t want to edit the theme since I want to be able to easily take advantage of upgrades (and not have to apply my diffs to future versions of the files). Eventually, I found a post that showed me how to do it via Code injection: Syntax highlighting on Ghost. I used the latest versions available, so my header looks like:

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.6.0/styles/github.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.6.0/highlight.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.6.0/languages/clojure.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.6.0/languages/groovy.min.js"></script>
  pre {
    word-wrap: normal;
    -moz-hyphens: none;
    -ms-hyphens: none;
    -webkit-hyphens: none;
    hyphens: none;
    font-size: 0.7em;
    line-height: 1.3em;
    pre code, pre tt {
    white-space: pre;
  • I managed to do something similar for Disqus comments as well. Rather than editing the theme, I added this to the Code injection footer in addition to their block of javascript:
    $("section.post-content").after('<div id="disqus_thread"></div>');

Migrating posts

I installed the Ghost plugin in WordPress and exported my data as a JSON file. The conversion from html to markdown didn’t go very well (especially for code blocks). I dug into the plugin, and even opened an issue on GitHub (https://github.com/TryGhost/wp-ghost-exporter/issues/7), but the project seems dead.

Rather than fix all of the markdown by hand, I found another converter that did a better job: Pandoc. I wrote a small script to use the existing export, but replace the markdown with a better conversion:

# reformat_markdown.rb

require "json"
require "open3"

def main
  parsed = JSON.parse(File.read(ARGV.first))

  parsed["data"]["posts"].each do |post|
    post["markdown"] = fix_markdown(html2markdown(post["html"]))

  puts parsed.to_json

def html2markdown(html)
  Open3.popen3("pandoc -f html -t markdown") do |stdin, stdout, stderr, wait_thr|

    return stdout.read

def fix_markdown(markdown)
  markdown.gsub(/ \{lang="(.*?)"\}/m, '\1')

main if __FILE__ == $0

Then, I loaded this JSON file into Ghost. Most posts looked ok, but I did have to fix the formatting on a few. I didn’t look through every post, however, so it’s likely that some of the older posts have incorrect formatting.

Migrating comments

I decided to switch my comments to Disqus for ease. I followed the Manual Import instructions, exported my data from WordPress, and imported into Disqus.


So far, I’ve been very happy with Ghost. The writing experience is really nice (side by side markdown editor and preview). And I’ve already done an upgrade simply by bumping the version number of the docker image.

I do wish I could customize my author page a bit more with links to twitter, github, etc. And I would like to add a sidebar with extra info about me and highlighted posts. I may consider switching to a theme which gives me some of this.