It’s important to ensure that when building things, iteration is fast. I invested some time in making sure that writing posts is as easy as possible. This post will outline how I wrote and deployed it.
Writing the post
I use a great static site generator called Hugo to render this blog.
First, I created a new post using the command
hugo new post/how-i-wrote-this-blog-post.md
This created a new file, content/post/how-i-wrote-this-blog-post.md
, that looked something like this
+++
date = "2016-03-31T22:20:00-07:00"
description = ""
draft = true
tags = []
title = "how i wrote this blog post"
topics = []
+++
I changed the various parameters in the front matter, then wrote the post in Markdown.
Once the post was completed, I changed draft
to false
and then committed to my GitHub repository. As soon as I did that, the build and deploy process started.
Building and Deploying
The build and deploy process includes three steps:
- Rendering the blog using Hugo
- Uploading to S3 and cleaning up old files
- Invalidating the CDN
In order to build and deploy, I use a service called CircleCI. This service builds based on configuration in the root of the repository in a file called circle.yml
. That file looks something like
dependencies:
pre:
- go get -v github.com/spf13/hugo
- pip install awscli
- aws configure set preview.cloudfront true
test:
override:
- hugo
deployment:
master:
branch: master
commands:
- aws s3 sync public/ s3://$BUCKET_NAME --delete --region $BUCKET_REGION
- aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DIST_ID --paths '/*'
Let’s break this down a bit.
Installing dependencies
We need to first install the build and deployment dependencies.
If you have a Go environment set up (like CircleCI does), fetching Hugo can be done with go get
.
go get -v github.com/spf13/hugo
We then need to use the AWS CLI to sync with S3 and invalidate the CDN. Installing the CLI is fairly straightforward, but CDN invalidation is a feature in preview, so we need to explicitly enable that for now.
pip install awscli
aws configure set preview.cloudfront true
Building
All that’s necessary to build is to run the command hugo
. By default, it will render the entire site to the public/
directory.
Deploying
I think a good workflow is to have everything on the master
branch continuously deployed. Here is the configuration for deployment:
deployment:
master:
branch: master
This specifies that whenever the master
branch is updated, a deployment should be triggered. To actually deploy, the following commands are used:
aws s3 sync public/ s3://$BUCKET_NAME --delete --region $BUCKET_REGION
aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DIST_ID --paths '/*'
The first command syncs the build directory with the S3 bucket used to store the site and cleans up all files that are in the bucket but aren’t in the build directory. The second command invalidates all files in the CloudFront distribution.
If you’re interested in more details about the CircleCI configuration, you can read about them here.
How this can break
There is one corner case that would result in some users seeing missing files and has to do with the invalidation delays.
- A file is deleted from the blog.
- This file isn’t present in the CDN edge location that a reader is using.
- The sync deletes the file from S3, which is linked to from another page.
- The reader clicks a link to the nonexistent file. CloudFront tries to fill the cache but fails and the user gets a 404.
In order to fix this, you could trigger an S3 sync without deletion, create the invalidation, wait for the invalidation to complete, and then trigger the S3 sync with deletions. However, I am not doing this because
- The frequency of deletions is really low
- CloudFront invalidations are really slow and I have limited CircleCI build capacity
- Users could see nonexistent pages anyway if they keep an old page open through the deploy process and request old resources
Want help setting up something similar?
This setup is ideal for a number of reasons:
- It’s easy to write as you only need to push to GitHub and can forget the rest.
- It’s fast; it takes less than the blink of an eye (~250ms) to download and start painting the web page. It also will be this fast worldwide (see the list of CloudFront edge locations).
- It’s low maintenance; no server administration required!
- It’s cheap; I pay less than $2 a month for the entire setup.
- It takes about 1 minute to build and deploy (excluding the CloudFront invalidation which takes a few minutes.)
If you’d like to set up something similar and have questions, just let me know!