Hosting a Hugo website on AWS using serverless

Posted by Chris Ibbitson on Sun, Apr 9, 2023

I thought I’d build out my own website again. The last time I did this was using Geocities about 20 years ago (wonder how many people will remember Geocities)! This blog talks through how I’ve built it out, the different solutions I’m using and the tweaks I needed to get it all working.

The following is my experience of building out this site using Hugo, AWS and GitHub. It isn’t a formal guide from AWS - the views are all my own

Introduction

Before we get into what I’ve built and how I’ve built it, it is useful to provide some context on what both Hugo and AWS are.

What is Hugo?

Hugo is a fast modern static site generator, writen in Go. In essence it is a static site generator - it doesn’t dynamically build a page when each visitor requests it, instead it builds pages when you create or update your content. The nice thing with Hugo is that you can host your sites anywhere (such as a S3 bucket on AWS), and it runs without needing any databases of external dependencies. Hugo also has a massive community of contributors for all sorts of things, including templates, layouts etc.

What is AWS?

So in Amazon Web Services own words, AWS is “the world’s most comprehensive and broadly adopted cloud platform, offering over 200 fully featured services from data centers globally”. In simpler words, it enables individuals, start-ups and large enterprises to access services ranging from compute and databases, to CI/CD pipelines to advance Machine Learning and everything in between.

Solution Overview

When I set out building my new website, I had a very simple set of requirements:

  • It needs to run on AWS
  • Needs to be as cost effective as possible
  • It must be able to be updated from anywhere
  • Needs to be as simple as possible

Following a lot of Googling over the Christmas holiday’s this led me to Hugo. Hugo ticked the box in that it created static files, which meant I could easily host them on AWS without needing too many services in the mix (thus keeping it simple and cost effective). I intended to use GitHub to store my source code in - this also meant I could edit the content anywhere I could access GitHub from. By leveraging two additional AWS services (CodeBuild and CodePipeline), I could then automate the deployment of new content to the website based on “commits” to the GitHub repository.

The following is a high-level overview of the services used:

High Level Solution overview

At a high level, I am using:

  • Amazon Route 53 to host my DNS entries (i.e. ibbi.me)
  • Amazon S3 bucket to host the static website content (however the bucket itself is only accessible from Amazon CloudFront)
  • Amazon CloudFront to distribute the static content in a secure manner.
  • A CloudFront Function to automatically update the default directory index.

Deployment Pipeline

And to manage automated deployment, I am using:

  • GitHub as a source code repository
  • AWS CodePipeline to build the workflow that builds code in AWS CodeBuild and deploys the code to the S3 Bucket hosting the site
  • A AWS Lambda to invalidate the CloudFront distribution to ensure users get the latest version of the Website

Building the site on Hugo

So building on Hugo is super easy. To start, visit the Hugo Quick Start. This talks through everything you need to do to build out a site (and test it) locally on your machine of choice. You can also add templates from the vast selection. I used the Puppet Theme.

Once I’d set up the site locally, I then cloned the Git repo to my Github account. From then on, I only edit Hugo and add content using Visual Studio Code and the Github repo. This means I can edit the content anywhere (including on the train via my iPad) and aren’t constrained to using the Laptop with VS Code on.

Setting up AWS to host the Hugo site

Three steps were needed to set up AWS to host the Hugo site:

  • Create the S3 bucket to host the site
  • Configure the CloudFront distribution to use the S3 bucket as it’s origin
  • Create a CloudFront Function to automatically set the default director index

Creating the S3 bucket

Creating a S3 bucket is pretty simple. I created a private bucket (in that no public access is available to it). You can follow the guide here. The reason I created the bucket as public was, I only wanted access to the site to be achieved via CloudFront. The reason for this was the additional protections CloudFront provides from a security perspective, as well as the performance improvements it offers (by caching content).

Configuring CloudFront

Once the S3 bucket was created, I then needed to create a CloudFront Distribution that used the S3 bucket as it’s origin. You can follow the guide here that outlines how to create an Origin that links to an S3 bucket.

Alternatively, this blog talks through how to create both the S3 bucket and the CloudFront Distribution, and also includes some example CloudFormation code.

Defining the CloudFront Function

Due to the way Hugo builds the site, each folder has it’s own “index.html”. CloudFront doesn’t resolve URL’s such as http://example.com/about/ to a default document index (such as index.html) so we need to use a CloudFront Function to manipulate the URI path to direct to the folder’s index.html.

Thankfully there is a great AWS blog that talks through exactly what I needed to do, which can be found here.

Configuring the deployment pipeline on AWS

To configure the deployment pipeline on AWS I needed to both create the pipeline, and also add in a Lambda function to invalidate the CloudFront distribution after every successful deployment. This additional step ensured that users were being directed to the latest content, and not cached copies.

Configuring AWS CodePipeline

AWS already have a great blog available that talks through building a CI/CD pipeline for Hugo websites. I followed this, with one minor tweak - rather than using AWS CodeCommit as the code repo, I instead used GitHub. This is pretty simple to do as CodePipeline supports different connections, including GitHub. Instructions to establish a GitHub connection can be found here.

Creating the invalidation Lambda function

I followed this great blog to create the Lambda function to undertake a CloudFront Invalidation and link it to the CodePipeline.