Migrating from Ghost Pro to Hugo static on AWS for 24 cents
Tags: aws, cloudfront, hugo, talkncloud
Published: 2021-12-31 05:28:33

Confession, I've been using a managed service provider for my blog instead of self hosting on AWS...feels good to get that out. I've been using [Ghost Pro](https://ghost.org?via=mick55 "ghost pro") to host this site since I started over 12 months ago. You see when it comes to writing or starting anything really I'm a fan of just writing, don't sweat the details, sit down and start typing. Which is why I didn't self host, the plan was to get something going first to get my writing fix, pause, review and then migrate later to something else if I wanted to.

### Ghost Pro

It's better to write something than not at all. So when I started I did a quick scan of the market for the best "blog" platform that suited my needs and [Ghost Pro](https://ghost.org?via=mick55 "ghost pro") does that. It has heaps of integrations, looks great and the web CMS frontend is easy to use which means I can write and publish from anywhere.

One of the really cool things that I liked about Ghost Pro was that it's open source, so if going with a managed service isn't for you then you can self host. More information is available on the [ghost repo](https://github.com/tryghost/ghost "ghost repo").

Straight from the repo...

"The easiest way to get a production instance deployed is with our official Ghost(Pro) managed service. It takes about 2 minutes to launch a new site with worldwide CDN, backups, security and maintenance all done for you..."

You can see why I went with Ghost Pro in the first place, if you're just starting out and want to take that hands off approach I highly recommend going with [Ghost Pro](https://ghost.org?via=mick55 "ghost pro"). Great platform and helps the community:

[Ghost Pro](https://ghost.org?via=mick55 "ghost pro")

### Why switch?

To be honest, I have no problems switching back to Ghost Pro tomorrow or continuing to work with customers using Ghost Pro, it really comes down to your requirements, timelines and skills. If you have no time, go ghost, if you lack the skills right now, go ghost, if you have lots of common integrations go ghost. I actually tested out ghost pro with [ecommerce and wrote about when I started this blog](https://www.talkncloud.com/ghost-with-ecommerce/ "ghost with ecommerce"), my swag for sale on this site was easy as with ghost (reminds me, I need to add some cloud shirt designs, lacking right now).

If everything is so great with Ghost Pro than why switch? It's a good question. There are a few reasons, one of the main personal reasons is that I like to host my services. I write about these services, designs and share code I should be hosting them and I want to host them. One of the other key reasons is the underlying architecture design that suits my needs, my blogs doesn't change too much other than the written articles. I don't have loads of fancy integrations right now. I am a big fan of static sites because of the simplicity, fewer resources, security and low overheads. So my end goal was to always switch over to a static site of some sort in the future.

### What is a static site?

Firstly, what is a static website and how does it differ from a traditional website? A static site is exactly that, it can be a simple index.html flat file that is read from the client directly perhaps from S3 and displayed. There is no hosted compute server. A traditional website like wordpress would consist of a compute resource like EC2 and a database like postgres maybe using RDS. You can start to see the differences and benefits and we haven't discussed, scale, security or cost.

### Game Plan

I only really had a few basic requirements to get started:

* Some sort of migration from ghost to the new framework
* Static site framework that supports markdown
* Good page load speeds
* Theme or templating, preferred free themes or decent marketplace
* Something basic and automated that allows me to publish new content

When you start to break it down, this is a pretty tall order. When I was using Ghost Pro I'd simply go to the web admin interface, type away and publish content. I didn't need to think about any of this.

### The Stack

I started off by researching what others had done, I had made a bunch of assumptions based on what I know on how the site was going to be hosted but I was unsure on the static website framework.

I ended up selecting [hugo](https://gohugo.io/ "hugo") for the website framework because it's popular and it boasts about speed, who doesn't like a fast loading website? I went through a few different tests on my local setup to make sure it was a good fit. My only complaint so far is that I find the directory structure and templating syntax a little confusing compared to others I've used in the past.

Here is the full stack list:

* Hugo static website framework
* AWS S3
* AWS CloudFront
* AWS Route53
* CDK
* Github
* Forestry.io

### The Design

It's much easier to understand with a picture...

![talkncloud hugo static on AWS design](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a8dge71t8cu6c4yv4uib.png)
 
The design shows that readers will access the domain via the registration and records in Route53, this will direct them to our alias record CloudFront CDN and we'll add some certificates for security and so that visitors get that warm fuzzy feeling of security with a nice padlock in the browser and no errors. I'm using Lambda@Edge (cool stuff) here to fix up some of the path handling for the static site (more on this later) and then finally we hit the bucket with our static files.

There are a few other considerations here, like cloudfront OAI (origin access identity), we can force our users down the cloudfront path and remove the need for our bucket to be public which is great.

> Pretty cool, how do the files get updated though?

### Maintenance workflow

Most modern web applications need to be built now, by that I mean there is a build stage in the process. The site is often developed using a framework that speeds up development and code needs to be processed or built into an output that the browser understands e.g. our static files, like an index.html.

I don't want to be building on my local development setup and then dragging some files over to S3 and then potentially invalidating the cloudfront CDN so that my changes are live for viewers.

So, here is the workflow I've come up with for now:

![talkncloud hugo maintenance workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dcegms6ow81gdaqc22di.png)
 

So we can see from the workflow design above when admins (authors) make changes to the repo it will kick off the workflow. I've designed this one so that it only happens on a specific branch so that we don't waste unnecessary build time or deploy unwanted changes, so to start the workflow you'll need to raise a PR.

I've developed my own workflow for this one which basically installs and configures hugo, then runs the build, some basic testing and then finally the deploy. I'm using hugo's built-in deployment feature which works well with AWS S3, [here is an example from the config file from hugo](https://gohugo.io/hosting-and-deployment/hugo-deploy/ "hugo config"):

```toml
[[deployment.targets]]
# An arbitrary name for this target.
name = "mydeployment"
# The Go Cloud Development Kit URL to deploy to. Examples:
# GCS; see https://gocloud.dev/howto/blob/#gcs
# URL = "gs://<Bucket Name>"

# S3; see https://gocloud.dev/howto/blob/#s3
# For S3-compatible endpoints, see https://gocloud.dev/howto/blob/#s3-compatible
# URL = "s3://<Bucket Name>?region=<AWS region>"

# Azure Blob Storage; see https://gocloud.dev/howto/blob/#azure
# URL = "azblob://$web"

# You can use a "prefix=" query parameter to target a subfolder of the bucket:
# URL = "gs://<Bucket Name>?prefix=a/subfolder/"

# If you are using a CloudFront CDN, deploy will invalidate the cache as needed.
cloudFrontDistributionID = <ID>
```

You can see that you'll supply your bucket and cloudfront CDN, you can also have multiple environments e.g. prod, non-prod (targets). Simply running the hugo deploy will use this config with your AWS cli access to deploy and invalidate the CDN. Pretty neat.

So, now from here, all we need to do is make our changes in a normal source code repo, do a pull request and our workflow will take care of the rest. Once the actions have been completed the static website changes will be live. Easy peesy, fairly well automated.

### Client-side development

As I commented earlier, I write mostly in markdown, it works for me. Actually, when I was using [Ghost Pro](https://ghost.org?via=mick55 "ghost pro") I would mostly use the markdown editor for all my posts. But, now how do we make changes from the client, what does it look like?

![example md file for hugo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ungmpasrbwvy24kx3qwq.png)
 
The image above is a dummy markdown file I've written. I'm using Visual Studio Code and I've simply created a new plain text md file. Markdown has its own syntax which helps you style the document. I'll leave you to research that, it's straight forward and easy to hang of.

For the majority of the content, that is the words this will be fine, it gets a little harder when you need more complex styling. I've decided to trial [forestry.io](https://forestry.io/ "forestry io") for a couple of reasons:

1. Would I miss the web based editor from Ghost and can this fill the gap?
2. Future projects and working with clients

Forestry.io basically aims to fill the gap as a CMS for static site generators, this isn't just for hugo. It hooks into your code repository and puts a web CMS frontend around it so that you can manage your site.

![simple wysiwyg editor with forestry](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ukzt8zsrp5etc6q26i9e.png)
 
You can see here you get a simple WYSIWYG editor so that if you're not familiar with markdown you can jump straight. Even if you are familiar it can be time saving it other ways.

I might do a more detailed write up on forestry.io, I'm still trying it out, I have found some issues that I've overcome. It's not all it's cracked up to be and I don't know if I'll stick with it. I like the general idea and where it's headed though and it's certainly something I'll be keeping an eye on.

### Lambda@edge

Argh, this one kinda of grinds my gears a little. I like Lambda@edge don't get me wrong, I think it's quite powerful but it would have been great if AWS handled this out of the box knowing that this is for static site hosting.

Anyway, so, if you have a standard website serving a very flat static structure of one level e.g. /index.html, /contact.html then everything is fine but if you have sub directories like /blog/awesome-sauce/index.html then it doesn't work. To work around this you need to handle the path matching with something like Lambda so that it can access the files correctly.

Thankfully AWS have done a [pretty great write up on the problem](https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/ "aws lambda edge cloudfront ") with some example code that does work. Have a read, grab the code, change it as needed (if at all), problem solved, moving on...

### Migrating the data

One of my key requirements was to be able to migrate the data from Ghost to Hugo, there isn't much point migrating to a static design if you have to rewrite all of the posts.

Ghost provides an export function from within the admin panel which outputs a json dump of the content without the images. To get the images you just need to contact support and they'll provide a link with your site configuration so that you can download all of the content.

The Hugo open source community has provided a tool to migrate from ghost to hugo, you simply use the cli tool to point it at your ghost export and it will convert it for you.

[ghostToHugo](https://github.com/jbarone/ghostToHugo "ghost to hugo migration tool") is the converter, it didn't work for me, I found that if you had a bookmark with a nil description it would cause the tool to fail. I've fixed the bug and committed it back to the repo so it works if you have the same problem as I did. You can check out my [commit](https://github.com/jbarone/ghostToHugo/commit/5a36592edb20559c7a220c8f304b68ae2455cda9 "ghostToHugo bug") here.

The output of the data in Hugo format was mostly OK, I had some issues with some of the images that needed fixing but this was easy done with a bulk find and replace.

### Styling hugo

One of the things I liked about Hugo was that there were more open source created themes than others (to me anyway). This is great and helps when you're starting out to see what others have done with practical examples.

I tried a bunch of different themes, I didn't want to get to far into customization if I didn't have to but in the end I did. I landed on [geek-hugo](https://github.com/gethugothemes/geeky-hugo "geeky hugo theme") by [themefisher](https://themefisher.com/ "themefisher") it is open source and looked like a good starting point for me.

The majority of my time went into customizing the theme. The main changes I made were to convert it do dark mode, widen the formatting and add in some custom styling changes that I liked, human readable times etc, bump some of the margins around. It was straight forward enough to update. If you are starting out using this theme let me know and I can help out.

### Performance

Using Google Lighthouse you can run tests on your test which give you a wealth of information. If we look at general scores we can see that the site is hitting **high 80's** across the board which isn't bad at all, always room for improvement.

![talkncloud lighthouse performance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3sgpmy779d2x92zrvhd9.png)

### Costs

OK, so this will probably be a big draw card for many. When I was using [Ghost Pro](https://ghost.org/?via=mick55 "ghost pro") I was paying **$36 USD** per month, this was for the fully managed service that gave me full customization.

AWS Cost estimates for this stack:

#### Estimates

| Service | Comments | Cost (USD) |
| --- | --- | --- |
| S3 | 1GB | $0.03 |
| Cloudfront | 1GB, 100k reqs | $0.21 |
| Lambda | 100k reqs, 128MB, 3 sec | $0.00 |

Total: **$0.24 USD** per month

I've included some Cloudfront costs to cover readers that might hit the site from non-edge locations but just know that with Cloudfront you get 1TB and 10M requests in the free tier.

Note: None of these included domain registration. If you want to bring a custom domain remember to throw another $10-12 bucks per year on your final figures. Forestry.io and github are both free for this use case.

Per my previous comments, the money for Ghost helps the open source community so this isn't a direct comparison per say. It's fine to spend a little extra for good but you can use these numbers to compare with closed source enterprise offerings.

### Final thoughts

This pattern for static hosting is pretty standard on AWS, using this pattern reduces a lot of the maintenance and scaling considerations thanks to AWS and static frameworks. If you're looking for one of the lowest cost solutions this would certainly be up there, you can also host something like this on Github pages for free which is another option. I enjoy having control and flexibility over the architecture and once it's in AWS I can continue to expand into other services as needed. [Ghost Pro](https://ghost.org/?via=mick55 "ghost pro") is one of the best options if you want to be completely hands off and need a more end to end solution, a perfect option for non technical people and helps support the community.

Have a think about your current website or customer websites and see if static fits the needs, it is most definitely the right way to go if you can swing it.

I haven't provided a CDK project this time around, I'll spend a few minutes to get something out of the next day or two that I can share that should help people get going with AWS static hosting and hugo.