Migrating from Ghost to Hugo

This post describes my experiences in migrating this blog from Ghost to Hugo. At the same time I switched hosts from Azure to Netlify (with GitLab source control), as there’s no point paying for a VM to run a site once you’ve moved it from a DB backed app to a static site.

It looks like it has all gone smoothly but please let me know if you spot anything that is broken.


I’d been wanting to move to a static blog for a while, for maintenance, scalability, security and environmental reasons. However, there were parts of the site that had to be dynamic, which needed migrating or decommissioning first.

Dynamic sites are inefficient for content that is updated infrequently. Why burn server cycles (and electricity / carbon) when you can just re-generate when a change happens and simply serve the resulting output. If you would like to learn more you could check out the caching sections in my book.

The patching, breaking changes and unreliable scheduling of Ghost was becoming tiresome and I wanted a more developer-friendly work-flow where I can write offline. I prefer to write my blog posts in VS Code and publish them with git, not use a web app (yes, I know VS Code is technically a web app, but it’s offline first).


Preparation is key for a smooth migration and this is one of the reasons why it took so long. Deciding on a solution, coming up with a plan and testing it all take time, which takes away from other projects.


There were two main questions that I wanted answers to:

  1. What static site generator should I use?
  2. What host should I use?


After a bit of research I narrowed down the huge selection of options available to Gatsby and Hugo.

Gatsby creates a dynamic React app and so is compelling for me, as it’s very similar to what I do and would be easily hackable. However, it’s still quite new and not as polished as Hugo.

Hugo generates static HTML files and is more mature that Gatsby. It also has good migration support from Ghost and has the same Casper theme available that I was using on Ghost.

After giving them both a kick and performing a quick test migration on my Ghost export, I decided to go with Hugo for this project. I may try Gatsby for a green-field project in the future.


After a bit more research I narrowed down the huge selection of options available to GitLab Pages and Netlify.

As I was going to use GitLab to host my git repository anyway it made sense to use their CI pipeline and hosting too. GitLab pages is much better than the GitHub equivalent, for example you can add a custom TLS certificate. GitHub pages only supports Let’s Encrypt and that was a recent addition (previously you had to use something like Cloudflare for HTTPS).

Unfortunately, I couldn’t get it to work reliably. The first deploy would always fail and it would then retry and succeed. There were also some inconsistencies in the way Hugo generated the site. Perhaps the Docker container image I was using (monachus/hugo) had a different version of Hugo.

I’m confident that I could have solved these issues and tried other images but it was easier to just use Netlify.

Netlify works really well, although I did have an issue with them just deleting a test account and not even informing me. I also managed to break their FTP deploy with invalid characters but that just required renaming some tags and categories.


The next step was to decommission parts of the site that had a server-side component. For example, I ported the tube status to a client-side SPA using React. There were some other bits, such as a downloads page, but I won’t bore you with the details.


I took a full backup of old host’s file system and database. I also exported the Ghost data in JSON format and ran it through a Hugo migrator.


There will obviously be issues, so it’s now a process of finding them then re-running the migration and site generation until there don’t appear to be any remaining.


The actual migration is fairly straightforward. Once happy with the quality of the site on the temporary Netlify sub-domain, you just add a custom domain and alter the DNS records to point to the new host.

Netlify take care of provisioning HTTPS certificates with Let’s Encrypt. This can cause a slight delay but if you’re not worried about zero-downtime then this isn’t a big deal. Once TLS is set up then it is easy to redirect to HTTPS and set HSTS headers.

Final Thoughts

The whole process was surprisingly easy. The most time consuming part was iterating on the Hugo configuration to get the same result as the existing site (or close enough).

Netlify is very simple to get started with but if you want to do regular updates then you are better off using version control such as GitLab rather than directly uploading files. They also have some advanced features such as form submissions and serverless functions using AWS lambda.

Overall, it’s a much nicer experience than WordPress, Ghost or GitHub Pages. If you haven’t tried Netlify or GitLab then it’s worth giving their free tiers a go.

Hopefully this migration will encourage me to write more regularly, as I won’t need to be online. Time will tell.