(message "Blog")

Hugo - Build and Deploy Using Github Actions

·3 min read

As you probably know, this website is based on an amazing static website generator Hugo. I’ve been more than happy ever since I switched to it more than two years ago .

In the beginning, I deployed website directly from my local machine to the S3 and then invalidated Cloudfront cache as described in the article. This has been working well, but I had to automate it.

Website is in Git and sometimes when I’d do something to it I would build and deploy the website, but sometimes forget to push to Git (as we all do, right? :-) ). This was especially tricky when certain edits were done on the desktop, and I realized I have a typo, misspelling or could have phrased something better while at my laptop and not at home. Who wants that kind of hassle…

Anyhow, I decided to force myself to build and deploy only once things are pushed to Git, and to ensure that I had to have build and deploy setup configured there.

Github Actions

There’s nothing much fancy here, quite simple, monolith build and deploy process:

name: Deploy website to AWS S3

on:
  push:
    branches:
      - main
  workflow_dispatch:

# Required for Auth to AWS
permissions:
  contents: read
  id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these deployments to complete.
concurrency:
  group: "aws"
  cancel-in-progress: false

defaults:
  run:
    shell: bash

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      HUGO_VERSION: SOMEVERSION
    steps:
      - name: Install Hugo CLI
        run: |
          wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
          && sudo dpkg -i ${{ runner.temp }}/hugo.deb

      - name: Checkout
        uses: actions/checkout@v3
        with:
          submodules: recursive
          fetch-depth: 0

      - name: Build with Hugo
        run: |
          hugo

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-region: MYREGION
          role-to-assume: arn:aws:iam::MYAWSID:role/MYROLETOPUBLISHTOS3ANDINVALIDATECLOUDFRONTCACHE
          role-session-name: Github_to_AWS_via_OIDC

      - name: Deploy
        run: |
          hugo deploy

So basically process consists of:

  • spin up Ubuntu container
  • download hugo.deb and install it
  • checkout repository
  • hugo
  • login to AWS
  • hugo deploy

Authenticating to AWS

Interesting part to you might be how I authenticate to AWS. Instead of using static credentials I’m using OIDC provider in AWS . Once I created token.actions.githubusercontent.com provider under Identity providers I created the role which we’re using in Github Actions above.

Role is created with following trust policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::MYAWSACCOUNT:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                },
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:ivantomica/MYREPOSITORY:*"
                }
            }
        }
    ]
}

I could have restricted actions further, and specify that only main branch can authenticate to the AWS, but I wanted to have some freedom there for further experimentation.

This Role also has 2 policies attached to it:

  • policy to invalidate cloudfront distribution cache
  • policy to write to s3 bucket

And policies are as follows.

Cloudfront policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "cloudfront:CreateInvalidation",
            "Resource": "arn:aws:cloudfront::MYAWSACCOUNT:distribution/MYCLOUDFRONTDISTRIBUTIONID"
        }
    ]
}

S3 policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObjectVersion",
                "s3:ListBucket",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::mys3bucket",
                "arn:aws:s3:::mys3bucket/*"
            ]
        }
    ]
}

Conclusion

Voila, that’s it, nothing else required to automatically publish new version of the webiste every time I change something and push to Git. Whole Github Actions build/deploy process takes around 20 seconds which is quite fast by my standards.