August 29, 2020

Using Github Actions to Deploy Hugo static websites to S3

Squirrel jumping down from tree Photo by Andrey Svistunov

Workflow File

From the root of our Github repo, we need to create a directory named .github/workflows, and add a YAML file to this directory, such as deploy.yml.

In the file, we’ll need to specify how the workflow gets triggered, the environment used to run it, and the various steps required.

Workflow Name

First, we add a name for the workflow. This will be displayed in the repository’s Actions tab.

name: Deploy to S3

Events Triggering Workflow

Next we specify the event(s) that will trigger this workflow. In this case, it will only run when code is pushed into the master branch:

on:
  push:
    branches: [master]

Job Environment

We now specify a name for the one and only job needed for this workflow, and the environment in which it will run:

jobs:
  build-deploy:
    runs-on: ubuntu-latest

Checkout the Code

Now we define all of the steps, starting with checking out the code:

    steps:
      - uses: actions/checkout@v2

Download Hugo

Our next step has two command lines. The first calls the Github API to retrieve the tag of the latest Hugo release, looking for the .tag_name field in the response JSON. The second command in this step downloads and extracts the release.

      - name: Get latest Hugo version
        run: |
          LATEST_HUGO_VERSION=$(curl -s https://api.github.com/repos/gohugoio/hugo/releases/latest | jq -r '.tag_name')
          curl -sSL https://github.com/gohugoio/hugo/releases/download/${LATEST_HUGO_VERSION}/hugo_extended_${LATEST_HUGO_VERSION:1}_Linux-64bit.tar.gz | tar -xvzf-

Move Hugo

Now we can move the Hugo file where it can be executed from the command line, and print the version to verify.

      - name: Move Hugo executable file
        run: |
          sudo mv hugo /usr/local/bin/
          hugo version

Build the Site

The next step runs the hugo command which builds the site into the /public directory.

      - name: Build the Site
        run: hugo

Deploy to S3

Lastly, we have a two part step which deploys to S3, and invalidates the Cloudfront cache.

      - name: Sync with AWS S3 bucket & invalidate Cloudfront cache
        run: |
          aws s3 sync ./public s3://${{ secrets.AWS_BUCKET }} --acl public-read
          aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_ID }} --paths '/*'
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}

Note the lines above containing the syntax ${{ secrets.MY_SECRET_ENV_VAR }}. This allows us to access environment variables in the Secrets section of the Github repository. Secrets can be added from the Settings tab. In this example above, few of these values truly need to be hidden, but we make sure that any secrets referenced from our workflow are saved in our repo before pushing this file.

Complete Workflow

Once we finish, our workflow file will look something like the following:

name: Deploy to S3

on:
  push:
    branches: [master]

jobs:
  build-deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Get latest Hugo version
        run: |
          LATEST_HUGO_VERSION=$(curl -s https://api.github.com/repos/gohugoio/hugo/releases/latest | jq -r '.tag_name')
          curl -sSL https://github.com/gohugoio/hugo/releases/download/${LATEST_HUGO_VERSION}/hugo_extended_${LATEST_HUGO_VERSION:1}_Linux-64bit.tar.gz | tar -xvzf-
      - name: Move Hugo executable file
        run: |
          sudo mv hugo /usr/local/bin/
          hugo version
      - name: Build the Site
        run: hugo
      - name: Sync with AWS S3 bucket & invalidate Cloudfront cache
        run: |
          aws s3 sync ./public s3://${{ secrets.AWS_BUCKET }} --acl public-read
          aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_ID }} --paths '/*'
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}

Github Actions Interface

Once we commit and push this to our master branch, we click the Actions tab in our repo to locate our running workflow. Click on the workflow name, then on the job name to see the steps being executed.

Github Actions interface showing completed job Completed Job in Github Actions