When I originally set up this site, I used Azure Multistage Pipelines to build & deploy the site automatically. I recently switched the CI/CD process to replace Azure Pipelines with GitHub Actions and in this post, I want to show you how I did it.
Before I get into this, let me jump in front of a few questions you may have:
- Why aren’t you use Azure Static Web Apps? First and foremost, Azure Static Web Apps weren’t around when I launched this site. While a good option, at the time of writing this post they have some limitations. I personally prefer my set up as it gives me much more control.
- Why did I switch to GitHub Actions? GitHub is more natural to me and I like it’s simplicity over Azure DevOps. Azure DevOps is a great platform, but it’s overly complicated for most of my work.
The original post on Azure Pipelines already covered CI/CD & my requirements. Please refer to that post if you want to see how I roll things out:
GitHub Actions for Building & Deploying Hugo Sites
GitHub Actions are just like Azure Pipelines in concept. You create a workflow which is in a .github/workflows/*.yml file in your repo. This file contains one or more jobs & each job contains one or more steps.
You start by defining a schedule when you want the workflow to run & other global settings.
There are a few things to note here:
defaults
: This is where you can specify defaults for your entire workflow. Here, I say I want to use a bash shell whenever I run commands in the console.on
: This section is the trigger definition. Here I’m saying two things:- Run this workflow whenever there’s there’s a
push
to themaster
branch. - Run this workflow on a specific schedule using the cron syntax. I use Hugo’s feature where you can specify content should not be published before a specific time. With these constant builds, I get the scheduled publishing capability of a CMS without the cost of a dynamic site.
- Run this workflow whenever there’s there’s a
name: Build and deploy live site
defaults:
run:
shell: bash
# run this workflow on...
on:
# ... all pushes to master
push:
branches:
- master
# ... this schedule: 1m past the hour @ 7a/9a/12a/5a, M-F (ET)
schedule:
- cron: '1 12,14,17,22 * * 1,2,3,4,5'
Unlike Azure Pipelines where you have to specify the variables you want to use in your pipeline by specifying the variable group you created, your workflow has access to any of the Settings > Secrets defined in your GitHub repo. I’ve set a few of these on my repo:
I do have a bunch of environment variables set up, but here are the important ones for this blog post.
env:
HUGO_VERSION: 0.71.1
SITE_BASEURL: https://www.andrewconnell.com
AZURE_STORAGE_ACCOUNT: andrewconnellcom
# LIVE_AZURE_STORAGE_KEY: <secret>
Building Hugo Sites with GitHub Actions
Unlike Azure Pipelines, I have my entire build & deploy process in a single job. It’s simpler that way with how Hugo is set up with the deploy command.
Step 1: Download & Install Hugo
So, I start by cloning the repo & then downloading and installing the Hugo executable:
jobs:
######################################################################
# build & deploy the site
######################################################################
build-deploy:
name: Build and deploy
if: "!contains(github.event.head_commit.message,'[skip-ci]')"
runs-on: ubuntu-latest
steps:
######################################################################
# checkout full codebase
######################################################################
- name: Checkout repo codebase
uses: actions/checkout@v2
with:
fetch-depth: 1
clean: true
submodules: false
######################################################################
# download & install Hugo (line break added in URL for readability)
######################################################################
- name: Download Hugo v${{ env.HUGO_VERSION }} Linux x64
run: "wget https://github.com/gohugoio/hugo/releases/download/v${{ env.HUGO_VERSION }}
/hugo_${{ env.HUGO_VERSION }}_Linux-64bit.deb -O hugo_${{ env.HUGO_VERSION }}_Linux-64bit.deb"
- name: Install Hugo
run: sudo dpkg -i hugo*.deb
You’ll notice this job build-deploy
contains an if
condition. At the present time, GitHub Actions doesn’t support skipping a workflow run when the text [skip-ci] is present in the commit message. So, I added a condition to check and run this job only if that text isn’t found in the commit message.
Step 2: Build the site Using the Hugo Executable
With Hugo installed, build the site:
######################################################################
# build site with Hugo
######################################################################
- name: Build site with Hugo
run: hugo --baseUrl '${{ env.SITE_BASEURL }}'
Notice I’m using an environment variable passed into the job to control an argument on the Hugo executable.
Deploy Hugo Sites with GitHub Actions
With the site built, I’m ready to publish! Use the Hugo deploy CLI command to deploy the site. This will determine what files need to be uploaded or deleted from the Azure Storage Blob:
######################################################################
# deploy site with Hugo deploy comment (only deploys diff)
######################################################################
- name: Deploy build to static site (Azure storage blob)
run: hugo deploy --maxDeletes -1
env:
AZURE_STORAGE_ACCOUNT: ${{ env.AZURE_STORAGE_ACCOUNT }}
AZURE_STORAGE_KEY: ${{ secrets.LIVE_AZURE_STORAGE_KEY }}
BONUS: Annotate the release in Azure Application Insights
Azure Application Insights supports adding an annotation to your deployment. These will show up in your your AppInsights reports as little callouts in the graphs:
I’m doing this with a custom action from Wictor Wilen in my workflow:
######################################################################
# add release annotation to Azure App Insights
# > only annotate on push events & non [content] commits
######################################################################
- name: Annotate deployment to Azure Application Insights
uses: wictorwilen/application-insights-action@v1
if: "github.event_name=='push' && !contains(github.event.head_commit.message,'[content]')"
with:
applicationId: ${{ env.AZURE_APPLICATION_INSIGHTS_APPID }}
apiKey: ${{ secrets.AZURE_APPLICATION_INSIGHTS_APIKEY }}
releaseName: ${{ github.event_name }}
message: ${{ github.event.head_commit.message }} (${{ github.event.head_commit.id }})
actor: ${{ github.actor }}
In mine, notice the if
condition is making sure to only include annotations when the workflow trigger is a push (so I don’t add annotations for every scheduled build) and when I don’t have the string [content] in the commit message.
This ensures I only add annotations when I’m making a non-content change to the site which is what I want.
I’ve posted the full workflow at the end of this post
Wrapping it up
That’s it! I’ve written about a few other ways I was using Azure Pipelines with my Hugo site.
In the future days, I’ll show you how I moved those things over to GitHub Actions as well. This includes, for example, the post Automatically Reindex Hugo Sites with Azure Pipelines.
name: Build and deploy live site
defaults:
run:
shell: bash
# run this workflow on...
on:
# ... all pushes to master
push:
branches:
- master
# ... this schedule: 1m past the hour @ 7a/9a/12a/5a, M-F (ET)
schedule:
- cron: '1 12,14,17,22 * * 1,2,3,4,5'
env:
HUGO_VERSION: 0.71.1
SITE_BASEURL: https://www.andrewconnell.com
AZURE_STORAGE_ACCOUNT: andrewconnellcom
# LIVE_AZURE_STORAGE_KEY: <secret>
AZURE_APPLICATION_INSIGHTS_APPID: 63YoMama-2e26-W3rs-8Pnk-0fArmyB00tsd
# AZURE_APPLICATION_INSIGHTS_APIKEY: <secret>
jobs:
######################################################################
# build & deploy the site
######################################################################
build-deploy:
name: Build and deploy
if: "!contains(github.event.head_commit.message,'[skip-ci]')"
runs-on: ubuntu-latest
steps:
######################################################################
# checkout full codebase
######################################################################
- name: Checkout repo codebase
uses: actions/checkout@v2
with:
fetch-depth: 1
clean: true
submodules: false
######################################################################
# download & install Hugo
######################################################################
- name: Download Hugo v${{ env.HUGO_VERSION }} Linux x64
run: "wget https://github.com/gohugoio/hugo/releases/download/v${{ env.HUGO_VERSION }}
/hugo_${{ env.HUGO_VERSION }}_Linux-64bit.deb -O hugo_${{ env.HUGO_VERSION }}_Linux-64bit.deb"
- name: Install Hugo
run: sudo dpkg -i hugo*.deb
######################################################################
# build site with Hugo
######################################################################
- name: Build site with Hugo
run: hugo --baseUrl '${{ env.SITE_BASEURL }}'
######################################################################
# deploy site with Hugo deploy comment (only deploys diff)
######################################################################
- name: Deploy build to static site (Azure storage blob)
run: hugo deploy --maxDeletes -1
env:
AZURE_STORAGE_ACCOUNT: ${{ env.AZURE_STORAGE_ACCOUNT }}
AZURE_STORAGE_KEY: ${{ secrets.LIVE_AZURE_STORAGE_KEY }}
######################################################################
# add release annotation to Azure App Insights
# > only annotate on push events & non [content] commits
######################################################################
- name: Annotate deployment to Azure Application Insights
uses: wictorwilen/application-insights-action@v1
if: "github.event_name=='push' && !contains(github.event.head_commit.message,'[content]')"
with:
applicationId: ${{ env.AZURE_APPLICATION_INSIGHTS_APPID }}
apiKey: ${{ secrets.AZURE_APPLICATION_INSIGHTS_APIKEY }}
releaseName: ${{ github.event_name }}
message: ${{ github.event.head_commit.message }} (${{ github.event.head_commit.id }})
actor: ${{ github.actor }}