Deploying and managing a modern Hugo site on AWS Amplify

For a small project I’m working on, I wanted to set up content-oriented site comprising only of static content — Hugo was a natural choice. As I’m experimenting with AWS generally, I had a look at how AWS Amplify could be used to host it. Here, I discuss how I deployed the site to Amplify addressing these two points:

  • the basic process of deploying the Hugo site to AWS Amplify
  • supporting staging and production in this context — this involves configuration on both the Hugo side and the Amplify side

Content to test this out is provided in this repository on github.

Hugo 101

Hugo is a fast, content-oriented server which provides a great compromise between content management, styling and performance; content is easy to organize and edit using markdown, styling is flexible with a comprehensive set of customizable themes available and performance is delivered by generating static HTML/CSS assets from the input markdown which requires no backend per se.

Hugo was developed in go; until recently, this was insignificant as it was just a binary which did smart and fast content management — the introduction of Hugo modules, based on go modules means that go tools are now a dependency in managing Hugo projects as will be seen below.

Hugo modules are important as they are the new means to import content from other projects into a Hugo project; they provide the recommended mechanism to import themes for styling but also can be used to import other functionality such as support for contact forms.

Deploying Hugo content to AWS Amplify

AWS Amplify provides support for Hugo out of the box — it can auto-detect Hugo content and package its static content accordingly. All that is necessary is to connect Amplify to the repository containing the Hugo project and it will detect changes and redeploy on each update.

AWS Amplify provides Hugo version 0.75.1 by default which was released in Sept 2020. Although this is a modern version of Hugo, the AWS Amplify packaging does not include go tools which means that it is not possible to use the modules capabilities of Hugo with the default build container — the build fails. If you are not using Hugo modules, you should not encounter this issue.

To build a site which uses Hugo modules, then, it is necessary to add the go toolchain — this can be done in a couple of ways

  • create a new container for this purpose which contains both Hugo and the go toolchain
  • use the default container but add handling in the preBuild stage to download the go toolchain

I tried the first approach initially, thinking that it may result in smaller/more optimized build containers which would ultimately lead to faster builds. However, it was not possible to generate very compact containers using standard tricks (Alpine does not work with Amplify, for example) — the smallest container size I could achieve was about 600MB.

I put this bespoke build container on dockerhub and used that as the basis for the build; however, known issues with the interaction between dockerhub and Amplify arose — sometimes Amplify is unable to pull the container successfully from dockerhub due to the recent restrictions imposed by docker on the use of dockerhub without authentication. This resulted in an unreliable build process which was unworkable.

The alternative approach then was to add preBuild instructions to the standard Amazon Linux 2 build container which installed go tools before building the Hugo site; it was also necessary to modify the PATHto include the go toolchain. This can be seen in the yaml below.

version: 1
frontend:
phases:
preBuild:
commands:
- curl -OL https://golang.org/dl/go1.15.7.linux-amd64.tar.gz
- tar -C /usr/local -xzf ./go1.15.7.linux-amd64.tar.gz
build:
commands:
- PATH=$PATH:/usr/local/go/bin hugo
artifacts:
baseDirectory: /public
files:
- '**/*'
cache:
paths: []

With this set up, it was possible to build a Hugo website containing modules in Amplify.

Supporting staging and production versions of the site

Amplify provides support for differentiating between branches in a repository to enable separate instances of a frontend to be deployed, one for each branch. Thus, the production build can be deployed to one endpoint and a staging build can be deployed to a different endpoint. For testing, Amplify generates endpoints of the form https://<branch-name>.<app-id>.amplifyapp.com; hence it is possible to have, for example, https://main.<app-id>.amplifyapp.com and https://staging.<app-id>.amplifyapp.comfor branches main and stagerespectively. With Amplify’s Route 53 integration, this can easily be mapped to your own domains.

This fits well with a standard Hugo workflow, where changes to a site would be made locally in the first instance, then could be deployed to staging and finally to production. From the Hugo perspective this is supported by the notion of environments which can correspond exactly to the local context, staging and the production context. In Hugo, there is a_defaultconfiguration and environment-specific configurations with the latter overwriting any parameters defined in the former.

A key configuration parameter within Hugo is the baseURL — this may get hardcoded into the static content at site generation time and if it is incorrect, some site assets may not load. As the baseURL differs between the different deployments, a different baseURL can be defined in each environment. Note that if using the Amplify endpoints, it will be necessary to set up the application in Amplify first and then set the baseURL for the different environment as this cannot be known a priori; this will get picked up by Amplify when pushed to the repository.

It is then necessary to link the Amplify deployments with the environments defined in Hugo such that the appropriate content is built for each deployment — the HUGO_ENVIRONMENT variable can be set for each branch and provided to the build process using Amplify’s support for setting branch specific environment variables. When this linkage is made, Hugo will build the specific version of the website for staging and production.

If you want to look into this further, check out this git repo and the instructions there.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store