GitHub Actions & Hugo

My blog is statically generated by Hugo and I use GitHub Actions to automate publishing the updates. In this short article, I will walk you through how I’ve setup GitHub Actions to build and deploy this site to an S3 bucket.

AWS Setup

This workflow assumes you have already created an S3 bucket to host your statically generated website and Cloudflare is serving as the proxy. If you are curious how to do this, you can check out my previous post.

I am going to create an IAM user that GitHub will use to publish my statically generated website content. This user will need permission to read and write to my website’s S3 bucket.

Create an IAM Policy

First, I need an IAM Policy that allows an authorized user to read and write to my website’s S3 bucket. In my case, I created an IAM Policy named with the following JSON policy:

    "Version": "2012-10-17",
    "Statement": [
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
            "Resource": [

Note: This was my initial IAM policy and I may be able to remove additional permissions without affecting GitHub’s ability to publish my changes.

IAM Group

Next, I created an IAM Group named GitHub and attached my IAM Policy to it.

IAM User

Finally, I created an IAM User named svcGitHub and I set the user’s access type to Programmatic access only. I added the user to the GitHub group to ensure the user receives all the permissions assigned to the group.

The last step in the AWS Console was to go to the Security Credentials tab for svcGitHub and generate an Access Key/Secret Access Key. GitHub uses this token to connect to the S3 bucket to upload website changes.

GitHub Setup

Now that all the grunt work in AWS is complete, it is time to configure the GitHub repository and publish my content updates.

Hugo Configuration

I need to tell Hugo where to deploy my website to. This was done by creating the following deployment section in my Hugo config.toml file:

  name = "s3"
  URL = "s3://"

  pattern = "^.+\\.(js|css|svg|ttf)$"
  cacheControl = "max-age=31536000, no-transform, public"
  gzip = true

  pattern = "^.+\\.(png|jpg|gif)$"
  cacheControl = "max-age=31536000, no-transform, public"
  gzip = false

  pattern = "^.+\\.(html|xml|json)$"
  gzip = true

Adding AWS Keys to GitHub

GitHub needs to be able to authenticate to S3. To make this happen, I needed to add my svcGitHub account’s AWS Access Key & Access Secret Key to GitHub as “secrets”. Secrets are environment variables that are encrypted and only exposed to selected actions. According to GitHub, anyone with collaborator access to this repository can use these secrets in a workflow.

To add the secrets, I browsed to my blog’s GitHub repository Settings page and selected Secrets. I added secrets for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.


The GitHub Action

The secret sauce was adding a .github/workflows/build.yml YAML file to my Hugo content repository. This tells GitHub Actions exactly what to do.

In my build.yml (below), GitHub Actions installs Hugo v0.73.0, builds my site using the minification option, and then deploys it to S3 using the Access Key and Secret Access Key that I previously setup as GitHub secrets:

name: Build and Deploy
on: push
    name: Build and Deploy
    runs-on: ubuntu-latest
      - uses: actions/[email protected]
      - name: Install Hugo
        run: |
          tar xvzf ${HUGO_DOWNLOAD} hugo
          mv hugo $HOME/hugo
          HUGO_VERSION: 0.73.0
      - name: Hugo Build
        run: $HOME/hugo --minify
      - name: Deploy to S3
        if: github.ref == 'refs/heads/master'
        run: $HOME/hugo -v deploy --target s3
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}

git commit

Now that both AWS and GitHub are configured properly, GitHub automatically publishes my posts whenever I commit a new, non-draft page. If the build or upload process fails, I get an email from GitHub.

Future Plans

I routinely create blog posts ahead of time that I want published on different dates. (Full disclosure, I currently have 8 in draft and a list of 29 ideas for future posts.) I’m planning to setup a scheduled GitHub Action that kicks off 2 times a day (6am and 6pm) to execute the Hugo build & deploy steps. But I’ll leave that for another post…