Building this blog with Hugo + Azure (Part 1)

This is the first time I’ve set up a blog, so this post is going to be a bit about the technology I’m using, and how I integrated Azure DevOps pipelines to automatically build and deploy my changes.

Basically there’s a few parts:

  • Hugo to generate the static site
  • Azure blob storage to host the content
  • Azure Devops pipelines for continuous integration (CI) and continuous deployment (CD)
  • Content delivery network (CDN) to cache content and provide HTTP rewrite rules
  • Custom domain to point to the CDN

This post assumes you’ve got some knowledge about Azure DevOps and the Azure Portal, but there’s nothing really too advanced here.

Table of contents

Hugo to generate the static site

I’m not going to cover how to author a site using Hugo - reason being there’s far better resources out there that can describe this in a lot more detail.

I followed this tutorial as a guide to get going. The author pretty much had the exact same list of requirements I was looking for to build my blog:

  • Generate a static site to be hosted on Azure blob storage
  • Use markdown to write content
  • Be easily themable
  • Store in my own source control
  • Have the site automatically built and deployed when I merge my changes

Azure blob storage to host the content

Azure blob storage containers can now support hosting static websites. I’ve been using this for one of my sites umamitools.com for months now and so far it’s flawless & snappy.

Create the resource group

Open up the Azure portal and click Create a resource, then choose Resource group.

* A resource group is just a place where you can group related resource together, and doesn’t cost anything.

Creating a resource group

Depending on where you expect most of your traffic to come from, you may want to choose a particular data center region.

Create the Storage account

Once the resource group has been created, click on Go to resource group and click Add, then select Storage account. For the options, I’m going for:

  • Performance: Standard
  • Performance: StorageV2 (general purpose v2)
  • Replication: Locally-redundant storage (LRS)

Creating a storage account

Once the storage account has been created, go to the resource then under Settings click the Static website link, then in the blade click Enabled to enable our static website on the account.

Enter the following information:

  • Index document name: index.html
  • Error document path: 404.html (the theme I am using has this file under the layouts folder)

Then click Save and you should see an address in the Primary endpoint input - copy that for later.

Creating a storage account

What does the static website mean? There will be a special container named $web created in your blob storage - this is the root of the website and where you will upload the content to. Any requests made to the Primary endpoint address shown earlier will be directed to this container. Try browsing to the endpoint now in your browser - since you don’t yet have any content uploaded this is what you’ll see:

Creating a storage account

We’ll fix that in the next step.

Azure Devops pipelines for CI and CD

I’m using Azure DevOps to store my code, build, and deploy. I created two pipelines:

  • A build pipeline to run Hugo to compile the website assets and package them up into a build artifact
  • A release pipeline to deploy the build artifact to blob storage

Build pipeline

Sign in to Azure Devops and create a new project (or use an existing one). Click on Pipelines and then click on New pipeline. I like UI’s, so I then clicked on User the classic editor to create my pipeline, rather than deal with YAML.

Next step is to select where your sites' source code is hosted. Mine is in Azure Repos Git. Select your source location, branch, etc and click Continue.

Creating a storage account

On the template page, choose Start with an Empty job. I’m choosing to use a Linux build agent (ubuntu-20.04) for this pipeline.

Creating a storage account

Also if your site’s source references git submodules (eg for themes) then check the Checkout submodules option:

Creating a storage account

Now add a new Bash task with the following inline script:

# Download Hugo 0.82.0
wget https://github.com/gohugoio/hugo/releases/download/v0.82.0/hugo_0.82.0_Linux-64bit.deb -O ./hugo_0.82.0_Linux-64bit.deb

# Install it
sudo dpkg -i hugo_0.82.0_Linux-64bit.deb

# Run Hugo to build the site (will output in the ./public folder)
hugo

# Display contents of ./public folder for a sanity check
ls ./public -al

Add a Copy files task and configure it to copy the contents of the public folder to the deploy folder of the build artifact staging folder:

Copy build files to staging

Add a Publish buid artifact task and configure it:

Publish build artifact

Set the CI trigger so that the pipeline is automatically triggered when changes are made to the master branch.

CI trigger}

Click Save & queue to queue up a new build and let’s see how it goes:

Build run}

Success! We’ve got a build artifact produced with a deploy folder containing our built site. Now on to deploying our build artifact to our Azure storage account.

Release pipeline

Click on Pipelines -> Releases and create a new pipeline. Choose Start from an empty job. Configure it to use ubuntu-20.04 build agent.

Add a new artifact from the build pipeline and configure:

Add build artifact

Then click the trigger button in the upper left of the artifact and enable Continuous deployment trigger, setting the branch filter to the master branch.

Create service connection}

Click on Stage 1 and add a new Azure CLI task.

To enable the pipeline to interact with resources in your Azure subscription, in Azure Resource Manager connection you’ll need to click Manage to create a new service connection. Click Create service connection and then choose Azure Resource Manager

Create service connection}

Click Next then choose Service principal (automatic)

Create service connection}

If prompted, sign in with your Azure credentials. Select your subscription, the resource group containing your storage account, and give your service connection a name:

Create service connection}

Click Save and go back to your release pipeline configuration, click the Refresh button next to Azure Resource Manager connection, and select the newly created service connection.

Configure the rest of the task using the following:

  • Script Type: Shell
  • Script Location: Inline

And script content:

az storage blob delete-batch -s "\$web" --connection-string "$(AZURE_STORAGE_CONNECTION_STRING)"

az storage blob upload-batch -s "$(Release.PrimaryArtifactSourceAlias)/drop/deploy" -d "\$web" --connection-string "$(AZURE_STORAGE_CONNECTION_STRING)"

Create service connection}

Go to the Azure portal and in your storage account click on Access keys -> Show keys and copy the key1 connection string:

Create service connection}

Now back in the release pipeline configuration, click on Variables and add a new secure variable named AZURE_STORAGE_CONNECTION_STRING, paste in the connection string copied in the previous step:

Create service connection}

Click the padlock to make it secure! Now click Save and Create Release. If all goes well, it will deploy the blog to the storage account and your pipeline release will be green.

Let’s check to see if the files deployed correctly by browsing to the storage account static website endpoint that previously failed:

Create service connection}

Success! Next we’ll associate our custom domain with the static website endpoint.

Part 2: Building this blog with Hugo + Azure (Part 2)