Build And Release Hugo Site Using Azure Pipelines
In this post I’ll give a step-by-step explanation on how I build and publish my Hugo blog site to GitHub Pages using Azure Pipelines.
I’m using Hugo to create my blog site. It lets me create my posts in markdown and, with a simple command, generate a static website. This website is hosted on my personal GitHub Pages site. GitHub Pages allows me to turn content in a git repository into a website and host it online.
My goal was to publish changes to my blog whenever I push a change to the master branch of my blog repository. Before I go into the details of how I did this, let’s first have a look at what I started with.
I used the instructions on Hosting Hugo site on GitHub to create 2 git repositories.
blog repository containing my markdown files, Hugo templates, theme, etc.
<username>.github.io repository as the source of my GitHub Pages site.
ronaldbosma.github.io repository was included as a submodule in the
blog repository. This allowed me to run the Hugo command and generate the static website in the
ronaldbosma.github.io repository. Here’s a graphical representation.
As per the instructions I used a
deploy.sh script to generate the Hugo static website in the public folder and push any changes to
ronaldbosma.github.io. This has a few disadvantages though.
First off it’s a manual action which can be forgotten easily. Also, I’m a developer and I dislike doing things manually.
I also prefer to create separate branches for my posts so I can create a pull request and ask my peers for feedback. The
deploy.sh script can be run from every branch. Possibly publishing something I don’t want to or removing changes I haven’t merged from master yet.
The last problem I had was that for some reason the link from the
blog repository to the
ronaldbosma.github.io submodule seemed to disappear. Making pushing any changes to
ronaldbosma.github.io impossible. As a pragmatic solution I just removed and added the submodule manually, which was cumbersome.
What I wanted was to automatically build and release my blog whenever I push a change in the master branch of my
blog repository. I used Azure Pipelines to make this happen. The image below shows the various steps that are executed in the pipeline.
First a build pipeline is triggered whenever I push to master. Using a Hugo build task the static site is generated and published as an artifact of the build. When the build succeeds a release pipeline is triggered, which will take the site and push this into the
ronaldbosma.github.io repository using a Publish to GitHub Pages task.
Although I’m interacting with a git repository when I publish my site, I decided to put this step in a release pipeline and not a build pipeline. I’m releasing my site here. Not building it. This also allows me to add more stages to for example a testing environment or change the target location and type of my site altogether without affecting my build pipeline.
There’s another advantage to this solution besides having to do less manual work when publishing new changes to my blog. I can set a publish date in my posts. Posts with a publish date in the future will be ignored when generating my Hugo site. By scheduling my build pipeline to trigger at a regular interval I’m able to schedule posts to be published in the future.
In the next section of this post you’ll find a step-by-step explanation of what I did to create this pipeline.
- Step 1: Prerequisites
- Step 2: Remove submodule
- Step 3: Build Hugo site
- Step 4: Publish Hugo site
If you already have experience with Azure Pipelines in combination with GitHub repositories you can just setup the Prerequisites and jump right into configuring the build pipeline and release pipeline.
Step 1: Prerequisites
You’ll need an Azure DevOps project that has the ‘Pipelines’ Azure DevOps service enabled. You can enable this service in your Project settings.
There are also two extensions we’ll need to install from the Marketplace. These will be installed at the Organization level in Azure DevOps. So be sure you have the proper permissions.
Both extensions use PowerShell. So if you’re unable to install these extensions. Have a look at the PowerShell scripts in their GitHub repositories. You might find what you need.
1.1 Install the Hugo extension
We’re going to use the Hugo extension to generate our Hugo site. You can find it here in the Marketplace. You’ll have to sign in first before you can actually install the extension.
After signing in, click ‘Get it free’ and select your Azure DevOps organization and click ‘Install’.
1.2 Install the GitHub Pages Publish extension
We also need the GitHub Pages Publish extension to publish our Hugo site to GitHub Pages. You can find it here in the Marketplace. You need version 1.0.0 or higher. It has the option to change the branch to master instead of using gh-pages.
Click ‘Get it free’ again. Select your Azure DevOps organization and click ‘Install’.
1.3 Generate GitHub Personal Access Token
The Publish to GitHub Pages task requires a GitHub Personal Access Token to access the GitHub Pages repository. Create one using these steps.
- Browse to https://github.com/settings/tokens/new.
- Enter your password if requested.
- Enter a description.
- Check the public_repo option.
- Click ‘Generate token’ at the bottom of the page.
- Store the token for later use.
Step 2: Remove submodule
If you’ve included your GitHub Pages repository as a submodule to your blog repository like me. You can remove the submodule. Our new build pipeline will generate the site as an artifact that will be pushed to our GitHub Pages repository. So you don’t need the submodule anymore.
Follow these steps:
- Delete the ‘public’ submodule section from the .gitmodules file.
- Stage the .gitmodules changes:
git add .gitmodules.
- Delete the ‘public’ submodule section from the .git/config file.
git rm --cached public(no trailing slash).
- Remove the folder ‘.git/modules/public’.
git commit -m "Removed public submodule".
- Delete the now untracked public folder from your cloned repo.
Step 3: Build Hugo site
Now that we’re finished with the preparations it’s time to generate our Hugo site.
3.1 Create build pipeline linked to GitHub
We’ll start with a new build pipeline. You can follow the steps below or have a look at create your first pipeline for a detailed description.
- Open your Azure DevOps project.
- In the left menu choose Pipelines > Builds.
- Click the ‘New pipeline’ button.
- Select ‘GitHub’ as the source of your code.
- Select your blog repository containing your Hugo templates, themes, markdown posts, etc.
- Install Azure Pipelines in your GitHub account if you haven’t already.
- Authorize Azure Pipelines to access your GitHub resources.
- Select the pipeline template you want to start from. In our case the ‘Starter pipeline’ will do.
- An editor is opened where you can configure your pipeline using yaml. Remove all content. We’ll start from scratch in the next section.
3.2 Configure build pipeline
The first thing to do is configure a trigger. The following will make the build pipeline trigger whenever a change is pushed to master.
trigger: - master
The Hugo task we’re going to use, uses PowerShell. So we’ll have to use a Windows VM as a build agent.
pool: vmImage: 'vs2017-win2016' # need a Windows host because the Hugo task uses PowerShell
I’ve included the Hugo theme I use as a submodule in my blog repository. This step will checkout the blog repository including the theme submodule.
steps: - checkout: self displayName: 'Checkout repository including submodules' submodules: true # true so Hugo theme submodule is checked out
Next step is to generate the Hugo site. This will use the Hugo task we’ve installed earlier. The output will be generated to the artifact staging directory.
- task: HugoTask@1 displayName: 'Generate Hugo site' inputs: destination: '$(Build.ArtifactStagingDirectory)'
The last step is to publish the generated Hugo site as an artifact of our build. This will make it possible to use the result in a release pipeline when publishing the site.
- task: PublishPipelineArtifact@0 displayName: 'Publish Hugo site as artifact' inputs: artifactName: 'hugo-site' targetPath: '$(Build.ArtifactStagingDirectory)'
That’s it. You can click ‘Save and run’. Provide a comment and click ‘Save and run’ again. This will create an ‘azure-pipelines.yml’ file in your blog repository which contains your build pipeline. You can find the final azure-pipelines.yml for my blog site here.
Because of the trigger on master it will start a new build immediately. After your build succeeds it should contain an artifact as shown in the image below.
Step 4: Publish Hugo site
Now that we have a successful build we can create a release pipeline. It will take the generated Hugo site and publish it to GitHub Pages.
We’re going to use a task to push the Hugo site to a GitHub repository. If you want to push to a local repository or have some extra requirements. Have a look at this publish.ps1 script that’s used by the task.
To configure the release pipeline:
- Open your Azure DevOps project.
- In the left menu choose Pipelines > Releases.
- Click the ‘New pipeline’ button.
- Select the ‘Empty job’ template.
- Give the stage a name. E.g. ‘GitHub Pages’.
- Click ‘Add an artifact’.
Select ‘Build’ as the source type.
As the source, select the build pipeline we’ve just created.
Enter a different source alias if you want, like ‘blog’.
Enable the ‘Continuous deployment trigger’ so the release will automatically start after the build succeeds.
Open the Tasks tab for the ‘GitHub Pages’ stage.
Add the Publish to GitHub Pages task installed in the prerequisites and configure it:
- ‘Documentation Source’ should be
$(System.DefaultWorkingDirectory)/blog/*. Where blog is the artifact alias you’ve configured.
- Configure the ‘GitHub Personal Access Token’ as a secret using a variable.
- Set ‘Branch Name’ to master.
- ‘Documentation Source’ should be
You can now trigger a new build. After the build succeeds the release will start and publish any change in your site to GitHub Pages.
With this pipeline it’s a breeze to publish new changes to your blog. No more manual labor!