Using Hugo & AWS CodePipeline, CodeBuild, CloudFront & S3 to deploy a static site

Share on:

Retrospective: blog deployment model

Goal

Deploy a personal blog (in this case, troydieter.com) using the below requirements. I’ve used other deployment models, such as CI/CD pipelines – namely Jenkins to deploy static sites. Previous to the below approach, Ghost blog was used in conjunction with Nginx.

Requirements

  1. Initial infrastructure deployed using infrastructure as code.
  2. State is maintained in a remote provider.
  3. Low to nearly none operating costs.
  4. Optimized for fast delivery using a CDN.

Overview

  1. Changes are written to the static-content/content/post directory, committed and pushed to GitHub.
  2. AWS CodePipeline uses GitHub webhooks to watch and start the pipeline on changes.
  3. AWS CodeBuild sources from the GitHub repository, installs Hugo and generates the static sites.
  4. AWS CodeBuild uses Hugo to push to the target and invalidates the CloudFront distribution for near-time effect.
  5. AWS SNS topic invocation, users are subscribed for success\failure of CodePipeline.
  6. Voila! Low operating cost, highly available and easy to use static site generation!

diagram

Costs

Costs are generally under $10/mo, deployed in the AWS us-east-1 region.

Tool manifest

Terraform - Infrastructure as Code

  1. AWS S3 Bucket to maintain the created static content, bucket policy only allowing traffic from CloudFront origin access identity
  2. AWS CloudFront distribution using the above AWS S3 bucket as an origin
  3. AWS CloudFront origin access identity to secure access
  4. AWS Amazon Certificate Manager, generated certificate for *.troydieter.com and a few other SAN’s
  5. AWS Route 53 public forward zone for troydieter.com
  6. AWS Route 53 DNS records for www and the apex pointing to the CloudFront distribution
  7. Lambda@Edge function to accommodate standard redirects

A Lambda@Edge function that implements standard web server redirects that simplify directory handling.

For example, requests for URI paths that end in “/” are rewritten into “/index.html” before the request is passed on to the CloudFront Origin. You may think of that as an “internal” redirect in webserver terms.

URI paths that end in “…/index.html” are redirected to “…/” with an HTTP status code 301 (Moved Permanently). This is the same as an “external” redirect by a webserver.

URI paths that do not have an extension and do not end with a “/” are redirected to the same path with an appended “/” with an HTTP status code 301 (Moved Permanently). This is again an “external” redirect. (This may hide actual content from the Origin, if you use paths without extensions!).

Hugo - Static Site Generator

  1. Hugo Static Site Generator v0.74.3-DA0437B4
  2. Hugo Clarity theme

AWS - CodePipeline

  1. AWS CodePipeline to source from GitHub using oAuth
  2. AWS CodeBuild using a standard v4.0 CodeBuild managed instance (Ubuntu)

The following buildspec.yml file is used:

version: 0.2

phases:
  install:
    commands:
      - echo Entered the install phase...
      - apt-get -qq update && apt-get -qq install curl
      - curl -s -L https://github.com/gohugoio/hugo/releases/download/v0.74.3/hugo_0.74.3_Linux-64bit.deb -o hugo.deb
      - dpkg -i hugo.deb
    finally:
      - echo Installation done
  build:
    commands:
      - echo Entered the build phase ...
      - echo Build started on `date`
      - cd $CODEBUILD_SRC_DIR
      - cd static-content
      - rm -f buildspec.yml && rm -f .git && rm -f README.md
      - hugo --quiet
      - hugo deploy --target=troydieter --quiet
    finally:
      - echo Finished building and Hugo-based deployment of troydieter.com

AWS - SNS

Example of success message when AWS CodePipeline completes:

{"account":"redacted","detailType":"CodePipeline Action Execution State Change","region":"us-east-1","source":"aws.codepipeline","time":"2020-09-07T18:39:04Z","notificationRuleArn":"arn:aws:codestar-notifications:us-east-1:redacted:notificationrule/9f53b458369ecdbdfdg39e86c3d0013dcc61d2c781","detail":{"pipeline":"[troydieter.com](http://troydieter.com/)","execution-id":"84da1222-420f-42e4-bd47-redacted","stage":"Build","action":"Build","state":"SUCCEEDED","region":"us-east-1","type":{"owner":"AWS","provider":"CodeBuild","category":"Build","version":"1"},"version":1.0},"resources":["arn:aws:codepipeline:us-east-1:redacted:[troydieter.com](http://troydieter.com/)"],"additionalAttributes":{}}