Github Actions + Hugo + Terraform + S3
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
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:
- Create an SSL certificate on AWS Certificate Manager.
- 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
andAAAA
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:
- 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.
- 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 :