Hugo and GitHub Actions

Mon Jul 3, 2023 | 1140 Words | 6 Minute

Image of a Hugo and GitHub
© Peaceiris - GitHub

Introduction

Until now, I uploaded everything by hand for this blog. Of course, it would be easier to do this automatically, which is the topic of this post. The following features are important for me, and we will cover them all:

  • Every week on Monday the content should be uploaded
  • I want to start the pipeline in one click from the GitHub webpage or mobile app
  • When the content of the content folder changes, it should be uploaded automatically
  • Upload to a FTP-Server

Technology selection

There are great solutions which I could choose from e.g. Tarvis CI, GitHub Actions or CircleCI. Each of those are great and have individual features which are very well suited for different purposes.

As my build time is very small like two or three minutes I just went with GitHub Actions as there are 2000 Minutes/Month available. And the most important part is I wanted a free version. More importantly, I did not use GitHub Actions a lot - out of curiosity, it was very interesting to look into it.

GitHub Actions

GitHub Actions is the CI solution of GitHub, which is well documented. I will elaborate on how I configured the action, and in the end I will upload this file as a gist.

First, I had to create a .yml file in the following folder: .github/workflows/{name}.yml

After this you can set a name for the workflow like this:

name: Deploy Hugo site to Pages

Now it is getting more interesting. The on part is very important as it handles when the pipeline should run. For me, this is the place where I can achieve my goals.

  1. Only run the pipeline when something is pushed into the main-branch and the content of the content folder changes.
  2. Run every Monday at 12am
  3. Manually run the pipeline/workflow
on:
  # Runs on pushes targeting the default branch
  push:
    branches: ["main"]
    paths:
      - 'content/**'
  # or normal schedule each monday at 0:00
  schedule:
    - cron: '0 0 * * MON'

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

The schedule part can be any cronjob, as you would normally create one in Linux. It is a list, so you can even specify many cronjobs, e.g. you could run it on Monday and Tuesday etc.

The workflow_dispatch enables that you can manually run the workflow in the actions tab.

There are many more options to handle the on part, but to get more information, I would encourage you to read the documentation.

After this, you can tell the workflow which shell should be used. This is not a necessity, but it helps if you have a specific shell you like to use.

# Default to bash
defaults:
  run:
    shell: bash

The last part is the jobs stage, which is the core of GitHub Actions. As I am working on ubuntu-latest I want the build stage to run on the same system which you can specify with runs-on. You can change this in every job as you like.

With env you can handle environment variables for a specific pipeline, which for me was the HUGO_VERSION.

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    env:
      HUGO_VERSION: 0.114.0
    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: Install Dart Sass Embedded
        run: sudo snap install dart-sass-embedded
      - name: Checkout
        uses: actions/checkout@v3
        with:
          submodules: recursive
      - name: Install Node.js dependencies
        run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
      - name: Build with Hugo
        env:
          # For maximum backward compatibility with Hugo modules
          HUGO_ENVIRONMENT: production
          HUGO_ENV: production
        run: |
          hugo \
            --minify           
      - name: Sync files
        uses: SamKirkland/[email protected]
        with:
          server: ftp.myserver.com
          username: ftp_username
          password: ${{ secrets.ftp_password }}
          local-dir: ./public/

Each step will be displayed in the pipeline as it is easier to see what happens when and you can specify and split everything clearly.

The first step is to install the hugo-cli which is a simple wget command. There, I use the env variable from before to explicitly use the correct Hugo version.

The second step is to install dart-sass if you need this. This can possibly be removed if you do not sass/scss. For completeness, I included it here.

After this, I checkout the repository with an already available GitHub Action actions/checkout@v3. Without this checkout, you can not work with your repository. Normally, you’d use node with sass. That is why the next step is to install Node.

When all this prebuild stuff is done, we can finally build the static site with Hugo.

If the build finishes successfully, we can upload the public folder to the ftp server. Here I use the action SamKirkland/[email protected] where you can specify a lot, but the minimal stuff is included in the last step.

Special thing is the password, as it should not be written in the file directly. Best practice is to go into the settings in the GitHub repository. There you have to select the Secrets and variables and under this topic select actions. There you can create a new repository secret.

The complete .yml looks like this:

name: Deploy Hugo site to FTP

on:
  # Runs on pushes targeting the default branch
  push:
    branches: ["main"]
    paths:
      - 'content/**'
  # or normal schedule each monday at 0:00
  schedule:
    - cron: '0 0 * * MON'

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# 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 production deployments to complete.
concurrency:
  group: "pages"
  cancel-in-progress: false

# Default to bash
defaults:
  run:
    shell: bash

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    env:
      HUGO_VERSION: 0.114.0
    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: Install Dart Sass Embedded
        run: sudo snap install dart-sass-embedded
      - name: Checkout
        uses: actions/checkout@v3
        with:
          submodules: recursive
      - name: Install Node.js dependencies
        run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
      - name: Build with Hugo
        env:
          # For maximum backward compatibility with Hugo modules
          HUGO_ENVIRONMENT: production
          HUGO_ENV: production
        run: |
          hugo \
            --minify           
      - name: Sync files
        uses: SamKirkland/[email protected]
        with:
          server: ftp.myserver.com
          username: ftp_username
          password: ${{ secrets.ftp_password }}
          local-dir: ./public/

Conclusion

As we have discovered, it is very easy to use GitHub actions, and it can easily be build and deployed. The expense to create all this is minimal, and I encourage everyone to simply try and use GitHub actions. As I build the pipeline, everything went smoothly and no errors occurred for me. If anything fails for you, just write a comment here or contact me directly, as I am more than happy to help you.

comments powered by Disqus