Building a static site with Hugo, Terraform, and AWS
posted | about 8 minutes to read
tags: amazon web services terraform hugo devops tutorial web hosting web development
If you’re a frequent visitor to my blog, you may notice that it has a very new look as of today. This facelift isn’t just cosmetic or just related to any personal disclosures (although I’ll admit those played a role) - I’ve actually changed the framework that the blog runs on from Wordpress to Hugo as part of my exploration of serverless computing and web hosting. Hugo’s a static site generator, which means that instead of having a database and a comparatively slow (and potentially vulnerable to security exploits) Wordpress install hosting my site, I just have a bunch of raw HTML pages. You might think this sounds much less maintainable, but you might be surprised - it’s actually very easy! I simply write my blog posts in Markdown and Hugo compiles it all into static pages for me automatically. Hugo has a good initial tutorial as well as some great documentation to help you get started on their end, but today I want to take you through moving your existing Hugo site up into the AWS cloud in a maintainable, easy fashion.
Before we get started actually putting everything up, I want to start by explaining my philosophy in terms of how I designed the hosting stack. The idea was basically to minimize costs while also preserving ease of use for the website developer. To that end, I put together a list of expectations, and identified AWS technologies that could help me achieve those expectations:
- Static content only - S3
- Globally available - CloudFront
- Available over SSL - Amazon Certificate Manager
- Simple dev->production workflow - CodeCommit + CodePipeline
All of these were pretty straightforward to identify, but I didn’t want to have to spin them up individually or redo the entire stack by hand if I decided to build another Hugo site in the future. I played around with CloudFormation for a while, but found it a bit too clunky to really use effectively (although this is probably personal preference). Instead, I went with Terraform, which is a third-party offering that works with multiple cloud platforms. I found the syntax elegant and easy to understand, and was able to define my entire technology stack in less than a day. I’m sharing that Terraform template with an accompanying Lambda function and AWS CodeBuild buildspec.yml here - they’re the exact same files I used to spin up this site.
Terraform and Infrastructure As Code
If you’re at all familiar with AWS, I think you’ll find really easy to grasp. The general idea is that you’re actually building out your AWS infrastructure block by block, adding configuration options for each of the things you want to build. This means that your infrastructure is much more maintainable; instead of having to use the AWS tag system or Visio charts or a spreadsheet or whatever to keep track of your AWS assets, you can have a concise code-based document that explains exactly what your infrastructure looks like, and by updating that document and re-applying, you can make sure that your infrastructure always matches what’s documented in the Terraform file. Terraform will also take care of actually spinning up each individual piece of the stack while also being aware of dependencies: for example, it’ll wait until the S3 bucket is created before spinning up the CloudFront distribution. It’s a really neat tool that works extremely well and can save you a lot of work in the long run. I’m going to go through the file that I shared very briefly, then discuss how to spin up the stack.
Starting at the top of the Terraform template I provided above, you’ll notice some variable
blocks. These blocks will prompt you to enter values at the beginning of the setup, or default to their listed default value if you don’t provide values to go with them. Pretty self explanatory, but extremely important. Take special note of the domain
variable as we’ll be revisiting that one a lot as we go through the file.
After the variable
blocks, we briefly set up the AWS provider
, which just tells Terraform that we’re going to be building on AWS. Nothing spectacular here. Then we move on to a data
block which is referencing a CodeCommit repository. We actually need to go create this repository. We could set it up to be part of the build, but we very much do not want to do this because if we do, it’ll be managed by the Terraform file. That means if we ever want to spin down the infrastructure later, we’d also be deleting the Git repository. We don’t want to do that, because we may want to spin the site back up in the future, and we don’t want to lose our site files. The best way to do that is to create the repository outside of Terraform. You’ll notice in the script that the repository name needs to be ${var.domain}
, which refers to the domain
variable that we saw at the beginning of the file - so go ahead and go into CodeCommit and create that repository now.
Once you’ve got that done, we can move on to the rest of the Terraform file. The rest of the stuff you’ll see is stuff that Terraform will be creating on its own - mostly resource
blocks, but also some data
blocks that contain IAM policies that we apply as part of other resources
. I won’t go through all of that - I’m assuming you have some basic familiarity with AWS technologies - but feel free to tweak anything in here according to your needs. In short, the template as written executes the following actions:
- Creates a Route 53 DNS zone under the domain name of the site
- Creates an S3 bucket to hold the site files
- Creates a Lambda@Edge function to handle redirection rules
- Creates a CloudFront distribution backed by the S3 bucket
- Creates an access identity for CloudFront to access the bucket, and adds an appropriate bucket policy
- Adds DNS records for CloudFront for the apex and www records of the DNS zone
- Requests a new certificate from Amazon Certificate Manager
- Adds the DNS records from the certificate request to the Route 53 zone and automatically validates the certificate
- Creates an IAM role for CodeBuild and CodePipeline
- Creates a CodePipeline to compile the Hugo site into raw HTML and upload it to the S3 bucket, triggering on new commits to the CodeCommit repo’s
master
branch
Spinning Up The Stack
It’s pretty easy to actually use Terraform. You’ll need to download and install it per the documentation, then hop into the directory where you have your Terraform template downloaded and run terraform init
to get it all set up. terraform plan
will give you an outline of the changes that it’ll make, then you can run terraform apply
to actually run those changes. Once it’s complete, you’ll have your entire stack built out - and all you have to do is commit your Hugo files to your CodeCommit repo. (Make sure you add the provided buildspec.yml file to the root of your site so that CodeBuild will know what to do with your files. Obviously, if you’re using another static site generator like Jekyll or something, you can modify the buildspec to fit your needs.)
If everything works, you should be able to view your site live at the domain you set it up on immediately after committing your files to the Git repository - and every time you commit again (say, to add a new blog post), your site will update on the fly. If you ever want to spin down your hosting infrastructure, just run terraform destroy
and Terraform will ask you if you’re sure before tearing down all of the managed infrastructure. It will leave your CodeCommit repository in place, meaning that spinning your site back up is as easy as just running terraform apply
again.
This idea of being able to spin up and spin down the stacks at will is extremely convenient when you have multiple sites to manage. All you have to do is run terraform apply
or terraform destroy
with the proper input variables, and Terraform can automatically spin up or spin down whatever domain you want. This should make managing multiple websites a breeze!
Next Steps
There are a few things left. Spinning up a static site is all well and good, but it’s still just a static site at the end of the day. In a future post, I’ll discuss leveraging Lambda and the API Gateway service, along with some other AWS services, to add dynamic content like blog comments or a contact form to your static site within a serverless design. To do this, I’ll extend the existing Terraform template so that the whole thing remains easily maintainable.
I think that’s about it as far as spinning up a Hugo static site’s concerned, though. Let me know if you have any questions by reaching out on my contact page.