Github Actions for Automated Hugo Deployment Pipeline

Yogi Salomo Manguntang Pratama,DevOpsHugoGithub Actions

A while back, I built a website called english-speaking-korean.company (opens in a new tab) using Hugo, a static site generator written in Go. The idea behind this setup was to allow people to contribute to the website by writing Markdown files and sending Pull Requests to the repository.

One thing I really wanted to add to this project was a CI/CD pipeline. Basically, I wanted the website to automatically deploy every time someone merges a Pull Request. I had been putting it off for a while, but I finally got around to it recently.

Fortunately, Hugo has some handy features for building and deploying websites, so the implementation wasn't too difficult. Here's the final setup I used for the GitHub Action Configuration:

jobs:
  build_deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write # This is required for requesting the JWT
      contents: read # This is required for actions/checkout
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: true # Fetch Hugo themes (true OR recursive)
      - name: configure aws credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::808951188063:role/english-korean-companies-deploy
          role-session-name: deployWebsite
          aws-region: ap-northeast-2
      - name: Setup Hugo
        run: |
          sudo apt install hugo
      - name: Build Hugo
        run: |
          hugo --minify
      - name: Deploy to S3
        run: |
          echo >> config.toml
          echo "URL = \"${{ secrets.S3_URL }}\"" >> config.toml
          echo >> config.toml
          echo "cloudFrontDistributionID = \"${{ secrets.CF_DISTRIBUTION_ID }}\"" >> config.toml
          echo >> config.toml
          hugo deploy

Authentication to AWS

Setting up authentication for AWS is actually pretty simple thanks to the official AWS action provided by AWS itself. Instead of the traditional approach of storing the Access and Secret Key as environment variables, I opted to take advantage of the OpenID Connect feature offered by GitHub. This method offers an enhanced authentication process by granting a temporary authentication token to trusted entities like GitHub. It's a significant improvement, and I might consider writing a separate blog post on it. For now, here's how I utilize it in this context:

-   name: configure aws credentials
    uses: aws-actions/configure-aws-credentials@v2
        with:
            role-to-assume: ${AWS_ROLE_ARN}
            role-session-name: deployWebsite
            aws-region: ${AWS_REGION}
 

And don't forget to provide permissions to the Github Action job for the authentication token-related functionalities:

permissions:
  id-token: write # This is required for requesting the JWT
  contents: read # This is required for actions/checkout

Setup & Build of the Hugo Static Website

The setup and build process of Hugo is also quite straightforward. The Hugo tool can be easily installed from APT, and the build process can be executed with a single CLI function call. To generate the minified version of relevant outputs, I include the --minify flag.

- name: Setup Hugo
  run: |
    sudo apt install hugo
- name: Build Hugo
  run: |
    hugo --minify

Deployment to AWS & CloudFront

One challenge I encountered during this process was modifying the config.toml file to dynamically include the S3 and CloudFront information during the deployment process.

Hugo offers a feature to automatically deploy the generated website to various destinations, including S3, which we utilized for this project. However, I wanted to avoid hardcoding the S3 and CloudFront details directly in the config file, and unfortunately, .toml files do not support string interpolation from environment variables.

To overcome this, I devised a solution where I stored those values as secrets in GitHub Actions and appended them to the end of the config file during the GitHub Action job. One crucial step I nearly forgot was relocating the [[deployment.targets]] section to the end of the .toml file since the appended lines needed to be part of this section.

- name: Deploy to S3
  run: |
    echo >> config.toml
    echo "URL = \"${{ secrets.S3_URL }}\"" >> config.toml
    echo >> config.toml
    echo "cloudFrontDistributionID = \"${{ secrets.CF_DISTRIBUTION_ID }}\"" >> config.toml
    echo >> config.toml
    hugo deploy

While alternative approaches like using sed or other methods could be viable options, I found that the solution I implemented served its purpose effectively. So, for now, I will stick with the current approach. However, feel free to explore other methods if you believe they may better suit your needs in the future.

Closing

I'm delighted that english-speaking-korean.company (opens in a new tab) is now automatically deployed every time a PR is merged. This exercise allowed me to revisit Hugo concepts and explore the realm of OpenID authentication with AWS, making it an enjoyable learning experience that refreshed my knowledge.