Deploying a Hugo site with Github Actions, S3, Cloudfront and AWS IAM

GitHub Actions are a nice way to get “push to deploy” behaviour for a static site. Here’s a quick run-through of how I have it working for Hugo sites being deployed to AWS.

This assumes you have created an S3 bucket and Cloudfront distribution for the site, and have pointed the domain name to the Cloudfront distribution.

It’s best to have an isolated IAM user for deploying with minimal permissions. Create a new policy in AWS IAM, and give it minimal permissions for deploying the site.

Go to the IAM console: https://console.aws.amazon.com/iam/home

Create an AWS IAM policy with JSON like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:PutBucketPolicy",
        "s3:ListBucket",
        "s3:DeleteObject",
        "s3:PutObjectAcl",
        "cloudfront:CreateInvalidation",
        "s3:GetBucketPolicy"
      ],
      "Resource": [
        "arn:aws:cloudfront::{aws-account-id}:distribution/{cloudfront-distribution-id}",
        "arn:aws:s3:::{s3-bucket-name}",
        "arn:aws:s3:::{s3-bucket-name}/*"
      ]
    }
  ]
}

You need to replace {aws-account-id}, {s3-bucket-name} and {cloudfront-distribution-id}in that example. It’s probably easiest to paste in the JSON and then use the visual policy editor in AWS console to get the right resource ARNs in place.

Then create a new programmatic-access user and give it the new policy directly.

Save the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY for this new user.

You also need to enable static website hosting in the S3 bucket, and set the bucket policy to allow public access to the files:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::{s3-bucket-name}/*"
      ]
    }
  ]
}

The next thing is the Github Action config file, which lives at .github/workflows/build.yml in your repo:

name: Build and Deploy

on: push

jobs:
  build:
    name: Build and Deploy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Install Hugo
        run: |
          HUGO_DOWNLOAD=hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz
          wget https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/${HUGO_DOWNLOAD}
          tar xvzf ${HUGO_DOWNLOAD} hugo
          mv hugo $HOME/hugo
        env:
          HUGO_VERSION: 0.71.0
      - name: Hugo Build
        run: $HOME/hugo -v
      - name: Deploy to S3
        if: github.ref == 'refs/heads/master'
        run: aws s3 sync public/ s3://{s3-bucket-name}/ --delete --acl=public-read && aws cloudfront create-invalidation --distribution-id={cloudfront-distribution-id} --paths='/*'
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Again, you need to replace {s3-bucket-name} and {cloudfront-distribution-id} in that example. Set up AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as secrets in the Github Actions config using the values you got from AWS IAM for the new role.

This Github Action builds the Hugo site, syncs the generated site files with S3, and creates a Cloudfront invalidation to refresh the site.


Tech mentioned