This blog is built with Hugo. It’s an open-source static site generator that let’s me create blog posts using simple markdown files.
Up until a few days ago, I used to host this blog on Azure using their Static Web Apps offering.
It’s quite simple and easy to setup. It’s essentially a special Azure Blob Storage container. Deploying to it is reasonably simple as well using the Azure CLI.
To get a real domain to use with it, I needed to setup Azure Front Door.
The total cost of hosting the blog this way was around 80 Australian Cents. Very very cheap.
Well, good things don’t last. Microsoft decided to deprecate Azure Front Door (renamed it to Azure Front Door Classic) and started asking It’s customers to move to their new offering which is meant to be a replacement for the old service.
The new offering is Azure Front Door Standard/Premium and comes with a hefty increase in costs for me.
The classic offering pricing was mainly based on traffic which was very low for my blog. The new standard version has a base fee around $53AUD without taking into account traffic volume.
To me, the new cost is simply not worth it. I can, thankfully, afford that but it feels like a waste of money.
Hosting this blog isn’t complicated. I just need something to serve the static site files and this is why I’ve decided to self-host it on an old laptop that I’ve turned into a janky home server.
The first thing to solve here is how the blog should be served.
My home server hosts a number of self-hosted services for my personal use. I rely on Docker for that so I wrote a very simple Dockerfile to take the output of Hugo and build an Nginx Docker image.
FROM nginx:latest
COPY ./public/ /usr/share/nginx/html
Next, I needed to figure out how to get the image there.
My blog is also a git repository. I have a CI/CD pipeline that runs the Hugo build command whenever a merge happens on the master branch. It used to upload the resulting files to Azure Blob Storage but now it needs to build that Docker image then push it to some registry.
I thought about using a registry offered by Docker or Github for this. I decided to self-host the registry as well. No good reason behind it from a technical standpoint except for the learning experience.
I spun up a docker registry with proper authentication and updated my CI/CD pipeline with the following steps to login, build, and push the image:
name: Blog Actions
on:
push:
branches:
- master
permissions:
id-token: write
contents: read
jobs:
Build-And-Publish-Blog:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
with:
submodules: true # Fetch Hugo themes (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.145.0'
extended: true
- name: Build Blog via Hugo
run: hugo --minify --baseURL ${{ vars.BLOG_BASE_URL }}
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: ${{ vars.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_REGISTRY_USER }}
password: ${{ secrets.DOCKER_REGISTRY_PASS }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Blog Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
push: true
tags: |
${{ vars.DOCKER_REGISTRY }}/blog:latest
- name: Notify Watchtower to update MrCSharp Blog container
run: |
curl -H "Authorization: Bearer ${{ secrets.WATCHTOWER_AUTH_TOKEN }}" ${{ vars.WATCHTOWER_DOMAIN }}/v1/update
- run: echo "This job's status is ${{ job.status }}."
The final step in this pipeline is making a HTTP request to Watchtower which is an Open-Source software that hooks into the docker engine to automatically update the base image of running containers.
Watchtower checks for updates in intervals. It also has an endpoint I can call to start the update process immediately after the new blog image is pushed to the registry. That works great for my use case.
Alternatively, I could’ve simply used SSH instead to run the required set of docker commands to update the image and run it on the server. I didn’t go down this path simply because having Watchtower running is going to be very useful in the future for other services/apps I’m running.
With this setup, I’ve saved myself $50AUD and learned a few things along the way. There’s probably a few more things I could improve on this but I’ll leave that for another time.