Hugo is one of the most popular frameworks for creating JAM stack websites easily. It is powering thousands of websites as well as complex applications at the moment. Due to its high performance, it is almost overtaking wordpress deployments. This blog post explains how you can deploy a simple Hugo website using Github Actions and Github pages for free.

Hugo Overview

Hugo is a highly popular open-source static site generator (SSG) designed to create fast and flexible websites. Developed in the Go programming language, Hugo aims to simplify the process of building static websites by allowing developers to generate web pages from simple text files.

Unlike traditional content management systems (CMS) that dynamically render web content on each request, Hugo generates static HTML files during the build process. This makes the resulting website extremely fast, secure, and easy to host on any web server or content delivery network (CDN). It offers support for multi-language sites, powerful templating with reusable components, live-reloading for instant previews during development, and a vast ecosystem of themes and plugins created by a vibrant community.

Whether you’re building a personal blog, documentation site, portfolio, or e-commerce store, Hugo provides an efficient and scalable solution for generating static websites that are both visually appealing and performant.

Setup Project Directory

Let’s set up our project repository.

Prerequisites

  1. Hugo website
  2. Git

This post assumes that you already have a Hugo website ready to deploy.

If you’re using Github for your repository hosting, you can create a repository with name <username>.github.io. This is special name that Github pages use to host your website. This repo will be accessible at the URL https://<username>.github.io once it’s deployed. Go inside your Hugo website directory and follow below commands to set up your Github repo.

git init
git remote add origin git@github.com:<username>/<repo-name>
git add .
git commit -m "Initial Hugo website"
git push origin master

With above setup done, you should have a repository with name <username>.github.io in your Github account.

In your Github account, for the repository, change the visibility to public repository if it’s not public. Go to Seetings > Danger Zone > Change Visiblity > Change to public

Github Settings Github Change Visibility

Also change the Page source by going to “Settings > Pages” and set the “Source” to “Github Actions”.

Deploy using Github Actions

What is Github Actions?

GitHub Actions is a flexible automation framework built into the GitHub platform that allows you to define custom workflows for your software projects. With this feature, developers can automate common tasks, build, test, and deploy their applications directly from their GitHub repositories. It empowers developers to create complex workflow pipelines without relying on external tools or services.

Building Blocks:

  1. Workflow: A workflow represents an automated process consisting of one or more jobs performed on specific events or triggers. Workflows are defined using YAML files and can be customized to suit your project’s needs.

  2. Jobs: Jobs are units of work within a workflow that run in parallel by default and define the sequence of steps required to accomplish a specific task. Each job runs on its own virtual environment (runner) and can include multiple steps.

  3. Steps: Steps are the individual actions that make up a job. They represent tasks like building, testing, deploying, or sending notifications. You can use both built-in actions provided by GitHub or create your own custom actions for specific requirements.

  4. Actions: Actions are reusable building blocks used within workflows to perform various tasks within your development process. You can leverage pre-built actions from the GitHub Marketplace, community-maintained repositories, or create your own custom actions.

Create a file .github/workflows/github-pages.yml in your repository.

We first specify the general syntax for Github actions. These at least includes name and we also provided some default.

1name: Build & Deploy Site
2defaults:
3  run:
4    shell: bash

Next, specify when the workflow should run.

1on:
2  workflow_dispatch:
3  push:
4    branches:
5      - main

At high level, we have two jobs. One for setting up hugo and building the website and another for deploying the website to Github pages.

 1jobs:
 2######################################################################
 3# build & deploy the site
 4######################################################################
 5  build:
 6    name: Build and deploy
 7    if: "!contains(github.event.head_commit.message,'[skip-ci]')"
 8    runs-on: ubuntu-latest
 9    ... ...
10    ... ...
11  # Deployment job
12  deploy:
13    environment:
14      name: github-pages
15      url: ${{ steps.deployment.outputs.page_url }}
16    runs-on: ubuntu-latest
17    needs: build
18    ... ...
19    ... ...

In above code, we specify that both steps run on ubuntu-latest using runs-on and the second job deploy depends on build job using needs: build. Let’s go deeper into each job in detail. The build job has several steps which include checking out repository, downloading and installing hugo on our pipeline runner instance. Next we install dart-sass so that we can process sass files. We set up Github Pages, build the website using hugo command and upload the artifacts from ./public directory for Github pages.

 1  build:
 2    name: Build and deploy
 3    if: "!contains(github.event.head_commit.message,'[skip-ci]')"
 4    runs-on: ubuntu-latest
 5    steps:
 6      - name: Checkout repo codebase
 7        uses: actions/checkout@v3
 8        with:
 9          submodules: recursive
10          fetch-depth: 0
11      - name: Download Hugo v${{ env.HUGO_VERSION }} and Install
12        env:
13          HUGO_VERSION: 0.115.3
14        run: |
15          wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
16          && sudo dpkg -i ${{ runner.temp }}/hugo.deb
17      - name: Install Dart Sass
18        run: sudo snap install dart-sass
19      - name: Setup Pages
20        id: pages
21        uses: actions/configure-pages@v3
22      - name: Build with Hugo
23        env:
24          # For maximum backward compatibility with Hugo modules
25          HUGO_ENVIRONMENT: production
26          HUGO_ENV: production
27        run: |
28          hugo \
29            --gc \
30            --minify \
31            --baseURL "${{ steps.pages.outputs.base_url }}/"          
32      - name: Upload artifact
33        uses: actions/upload-pages-artifact@v1
34        with:
35          path: ./public

Next, the deploy job includes deploying the website to Github pages.

 1  deploy:
 2    environment:
 3      name: github-pages
 4      url: ${{ steps.deployment.outputs.page_url }}
 5    runs-on: ubuntu-latest
 6    needs: build
 7    steps:
 8      - name: Deploy to GitHub Pages
 9        id: deployment
10        uses: actions/deploy-pages@v2

The complete file looks like below.

 1name: Build & Deploy Site
 2defaults:
 3  run:
 4    shell: bash
 5# When the action should run?
 6# Here, we specify workflow should run on push to main branch
 7# We also specified it to run manually with empty "workflow_dispatch"
 8on:
 9  workflow_dispatch:
10  push:
11    branches:
12      - main
13
14# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
15permissions:
16  contents: read
17  pages: write
18  id-token: write
19# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
20# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
21concurrency:
22  group: "pages"
23  cancel-in-progress: false
24
25
26jobs:
27######################################################################
28# build & deploy the site
29######################################################################
30  build:
31    name: Build and deploy
32    if: "!contains(github.event.head_commit.message,'[skip-ci]')"
33    runs-on: ubuntu-latest
34    steps:
35      ######################################################################
36      # checkout full codebase
37      ######################################################################
38      - name: Checkout repo codebase
39        uses: actions/checkout@v3
40        with:
41          submodules: recursive
42          fetch-depth: 0
43      ######################################################################
44      # download & install Hugo (line break added in URL for readability)
45      ######################################################################
46      - name: Download Hugo v${{ env.HUGO_VERSION }} and Install
47        env:
48          HUGO_VERSION: 0.115.3
49        run: |
50          wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
51          && sudo dpkg -i ${{ runner.temp }}/hugo.deb
52      - name: Install Dart Sass
53        run: sudo snap install dart-sass
54      - name: Setup Pages
55        id: pages
56        uses: actions/configure-pages@v3
57      - name: Build with Hugo
58        env:
59          # For maximum backward compatibility with Hugo modules
60          HUGO_ENVIRONMENT: production
61          HUGO_ENV: production
62        run: |
63          hugo \
64            --gc \
65            --minify \
66            --baseURL "${{ steps.pages.outputs.base_url }}/"          
67      - name: Upload artifact
68        uses: actions/upload-pages-artifact@v1
69        with:
70          path: ./public
71
72  # Deployment job
73  deploy:
74    environment:
75      name: github-pages
76      url: ${{ steps.deployment.outputs.page_url }}
77    runs-on: ubuntu-latest
78    needs: build
79    steps:
80      - name: Deploy to GitHub Pages
81        id: deployment
82        uses: actions/deploy-pages@v2