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

      - main

# Required for Auth to AWS
  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.
  group: "aws"
  cancel-in-progress: false

    shell: bash

    runs-on: ubuntu-latest
      - name: Install Hugo CLI
        run: |
          wget -O ${{ runner.temp }}/hugo.deb${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
          && sudo dpkg -i ${{ runner.temp }}/hugo.deb          

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

      - name: Build with Hugo
        run: |

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v2
          aws-region: MYREGION
          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 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/"
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "": ""
                "StringLike": {
                    "": "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": [
            "Resource": [


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.