Deploy Hugo site to AWS S3

Hugo is a static site generator

Page content

When the site is generated with hugo it’s time to deploy it to some hosting platform. Here is a howto push it to AWS S3 and serve with AWS CloudFront CDN.

Hugo static site deployed to AWS

Prepare site

Will not be descibing here how to create Hugo site project or add articles or blog posts there. Assuming you have already done this.

If not, here is a Hugo quickstart

Deployment Options

There are several options available to deploy and host a Hugo generated site (https://gohugo.io/hosting-and-deployment/). I personally like

Below I am describing exactly this - last - method.

1. Create s3 bitbucket with relaxed permissions

goto: https://console.aws.amazon.com/s3

  1. create bucket with the name like site name, for example: “microsoft.com”

  2. click on the bucket => properties, down below => static website hosting static website hosting section

  3. click edit, then - enable, and “host a static website”

editing static website hosting

  1. also put there in index: index.html and in error: 404.html
  2. click save, remember “bucket website endpoint”, it would look like: http://microsoft.com.s3-website-ap-southeast-2.amazonaws.com
  3. goto bucket permissions, see the images below. public access must not be blocked.

Block public access - off 1

Block public access - off 2

  1. bucket policy (replace microsoft.com with your domain name):
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::microsoft.com/*"
        }
    ]
}

2. Create Certificate

goto AWS certificate manager https://console.aws.amazon.com/acm pick correct region.

  1. click request
  2. public certificate
  3. domain names: take two like: “microsoft.com” and “www.microsoft.com
  4. you can ask for more subdomains like “blog.microsoft.com”, “xmpp.microsoft.com” etc
  5. do the dns validation. It’s much easier to do/push this via Route53 console if your registrar is AWS.

3. Deploy Lamnda function to Lambda@Edge

The Lambda@Edge function takes will rewrite the Hugo QuickStart project urls for directories to a default object, index.html. That’s how Cloudfront serves the URI ‘/posts/my-post/’ with content ‘/posts/my-post/index.html’ returning 200 instead of 404.

Flavor Cafe (Scotch) Lambda@Edge Code

// Hugo on Cloudfront, Lambda@Edge function 
// Flavor Cafe (Scotch)
// @starpebble on github
//
// Two rewrite rules for hugo sub directory uri's.
// Example:
//   1. rewrite uri /posts/ to /posts/index.html
//   2. rewrite uri /posts  to /posts/index.html
//
// Add as many file extensions as you like for rule 2.
// uri's that end in a known file extensions are not rewritten by rule 2.

'use strict';

// @starpebble on github
// hugo flavor cafe (scotch)

const DEFAULT_OBJECT = 'index.html';

exports.handler = (event, context, callback) => {
  const cfrequest = event.Records[0].cf.request;
  if (cfrequest.uri.length > 0 && cfrequest.uri.charAt(cfrequest.uri.length - 1) === '/') {
    // e.g. /posts/ to /posts/index.html
    cfrequest.uri += DEFAULT_OBJECT;
  }
  else if (!cfrequest.uri.match(/.(css|md|gif|ico|jpg|jpeg|js|png|txt|svg|woff|ttf|map|json|html)$/)) {
    // e.g. /posts to /posts/index.html
    cfrequest.uri += `/${DEFAULT_OBJECT}`;
  }
  callback(null, cfrequest);
  return true;
}; 

4. Create AWS CloudFront CDN

goto https://console.aws.amazon.com/cloudfront

  1. Create Distribution
  2. create origin pointing to your s3 bucket

Create Origin

  1. Certificate for your site
  2. when it offers to convert to static website - agree
  3. create behaviour pointing to your origin, and Redirect Http to Https

Edit Behaviour

  1. below in behaviour/functions associations - in Origin Request - select your Lambda Function

Lambda function

  1. Save
  2. Goto your distribution’s General tab and copy your Distribution domain name. Should be looling like: asdfasdfasdf.cloudfront.net

Distribution domain name example

5. Point AWS Route53 DNS to your CloudFrount

goto https://console.aws.amazon.com/route53/v2/hostedzones

  1. create or click on your site’s hosted zone. Should be called like your site: for example: “microsoft.com”
  2. create “A” empty record pointing to your CloudFront distribution (Distribution domain name)
  3. create “A” “www” record pointing as alias to your first “A” record

6. Install aws cli

  1. Install aws client commandline tools https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
  2. configure aws cli connection
    1. check file ~/.aws/credentials, it should have something like
[default]
aws_access_key_id = .......
aws_secret_access_key = .......
  1. test the connection, type something like below to see your s3 bucket
aws s3 ls

7. Update your config.toml

  1. open your hugo.toml or config.toml in your hugo project
  2. add to the end (replace again microsoft.com with yours):
[[deployment.targets]]
# An arbitrary name for this target.
name = "lfs3"

# S3; see https://gocloud.dev/howto/blob/#s3
# For S3-compatible endpoints, see https://gocloud.dev/howto/blob/#s3-compatible
URL = "s3://microsoft.com?region=ap-southeast-2"

# If you are using a CloudFront CDN, deploy will invalidate the cache as needed.
cloudFrontDistributionID = 	""

If you keep your configs in config.yml, it should look like

deployment:
  targets:
    name: "lfs3"
    URL: "s3://microsoft.com?region=ap-southeast-2"
    cloudFrontDistributionID:	"E123123123"
  1. save this file
  2. compile your site
hugo
  1. deploy it with hugo deploy command
hugo deploy
  1. open your site url in browser to see if this all worked

I hope this little howto doc would help you. Have a great day!