Github Actions + Hugo + Terraform + S3

jerome.decoster
8 min readMay 27, 2021

--

Creation of a static website with Hugo. Host it on S3 with CloudFront. Use Terraform and Github actions to deploy it in a GitOps way.

The Goal

  • Create a static blog with Hugo
  • Host it on S3 using a CloudFront CDN and a domain name managed by Route 53
  • Doing this manually from the web browser interface
  • Redoing the same thing with Terraform
  • Redoing it using Github Actions to automate the deployment of new posts
  • The GitOps approach makes it possible to update the site or the infrastructure according to the path of the commited files
architecture

Install, explore and build the project

Get the code from this github repository :

We launch the site :

$ make dev

This script performs the following :

$ hugo server \
--buildDrafts \
--disableFastRender

We note the use of the --buildDrafts option which shows us all the posts, even the drafts, which will not appear in the build version.

The website at http://localhost:1313 :

The draft: true attribute is defined in the Front Matter of the last 2 posts.

Here is the example :

Here’s a blog content page :

To see the site without the drafts we can do :

$ make dev-nodraft

This command simply do this :

$ hugo server

We see that the 2 most recent articles have disappeared :

Creating the S3 bucket

I’m going to create a static S3 site that will be associated with my domain name jeromedecoster.net :

I create a bucket www.jeromedecoster.net :

I authorize all public access :

The bucket is created :

I activate the static website hosting and I indicate the index page and the error page :

Creating the SSL certificate

For my site to support HTTPS, I need to:

  1. Create an SSL certificate on AWS Certificate Manager.
  2. Create a CloudFront distribution and associate it with this certificate.

In order to associate our SSL certificate with our CloudFront distribution, our certificate must be created in the us-east-1 region :

I start creating the certificate :

I enter 2 domain names jeromedecoster.net and *.jeromedecoster.net :

I choose DNS validation method :

The validation is now awaiting an action :

I need to create a specific CNAME record in Route 53.

I just need to click this button to validate the 2 domains at the same time :

I can see that a CNAME record was added in Route 53 after the click :

After a few seconds, I refresh the interface to see that my certificate has been validated :

Creation the CloudFront CDN

We are now creating the CloudFront distribution :

Warning : for the Origin Domain Name parameter, you MUST NOT CHOOSE the S3 bucket listed in the drop-down list !

The drop-down list suggest :

But you have to indicate :

I also choose Redirect HTTP to HTTPS :

For Alternate Domain Names, I indicate www.jeromedecoster.net.

I choose Custom SSL Certificate and I select my certificate :

I leave the other parameters by default and I validate the creation of the distribution :

I copy in my clipboard the Domain Name URL of my distribution :

Finalization on Route 53

I will now create a Record Set in Route 53 to route the traffic to my distribution :

I indicate www and I create a type A Record Set.

I choose Alias : Yes.

I see that my CloudFront distribution does not appear in the drop-down list.

This is not an error. It actually takes a while for this to become visible :

I can however directly paste the URL Domain Name of my CloudFront distribution that I had copied to my clipboard a few steps above.

I validate by clicking Create :

I do the same thing again to create a type AAAA Record Set :

Putting the site online

I will now generate the static site with Hugo :

$ make build

This command simply executes :

$ rm public \
--force \
--recursive
$ hugo

Files are generated in the public directory :

I will now upload these files to my bucket :

$ make upload

This command simply executes :

$ cd public
$ aws s3 sync --acl public-read . s3://www.$APEX_DOMAIN

The variable $APEX_DOMAIN is declared in the make.sh file :

If I display the URL https://www.jeromedecoster.net in my browser :

Using Terraform

We have just seen all the manual steps necessary to put a static site online.

We are now going to recreate this site identically using Terraform and aws cli in order to automate this task.

I start by destroying all my previously created resources :

  • The CloudFront distribution
  • The SSL certificate
  • The S3 bucket
  • The CNAME, A and AAAA Record Sets

Important: I choose to NOT MANAGE the creation of the SSL certificate by Terraform but to do it with aws cli. I also do NOT CREATE a Hosted Zone with Terraform.

Even if the creation / destruction of these resources is technically easy to do with Terraform, it seems preferable to manage it manually or via a bash script for the following reasons:

  1. You probably already have an existing a hosted zone. It is therefore not necessary to recreate another dynamically. Especially since each hosted area is billed monthly.
  2. Creating a certificate to associate it with a hosted zone is not a recurring task. Once created, there is little chance that you will want to delete it. We therefore avoid removing it unnecessarily by executing terraform destroy.

I create my certificate :

$ make create-certificate

This command executes this script :

We don’t want Terraform State files to be on our machine. We want them to be hosted on S3.

We define it in the main.tf file :

So we create a specific bucket to receive the Terraform State files :

$ make tf-setup-backend

This command simply executes :

We can now initialize Terraform :

$ make tf-init

This command simply executes :

Let’s take a look at some excerpts from Terraform files.

We use a trick to create resources in 2 different regions in main.tf :

I create a Record Set using an already existing Hosted Zone in route53.tf :

Creating the bucket for static website hosting in s3.tf :

Creation of the CloudFront distribution in cloudfront.tf :

Now we can create the infrastructure :

$ make tf-apply

Then upload the files :

$ make upload

If I display the URL https://www.jeromedecoster.net in my browser :

Website update with Github actions

I create a workflow to rebuild the site and deploy it automatically after each update with cd.yml :

Let’s look at some excerpts.

The workflow will only be triggered if files are modified after a push action in these directories :

We use the peaceiris/actions-hugo action to install Hugo.

Then we generate the website very simply :

We deploy the files in the S3 bucket:

We launch a CloudFront invalidation on specific files to update our site quickly :

I create project specific AWS accesses :

$ create-user

This command executes this script :

I add some secret variables to my github project :

I modify a post that had remained in draft: true.

I set now draft: false :

After the commit, the action is executed quickly :

I see the new blog post, my website is updated successfully :

Infrastructure update with Github actions

I create a workflow to update the infrastructure with Terraform in infra.yml.

Let’s look at some excerpts.

The workflow will only be triggered if files are modified after a push action in this directory :

Some of the variables used by Terraform are defined :

Terraform is installed using the hashicorp/setup-terraform action, and then initialized with the remote state backend :

We apply the transformations :

To test this action I create an additional bucket by uncommenting these lines in the s3.tf file :

This bucket is useless for my project, it’s just to test the process.

The action is quickly executed successfully :

The bucket has been created :

--

--