Featured image of post Built with Hugo, Cloudflare and Love

Built with Hugo, Cloudflare and Love

In this post, I will describe my current setup, which I am very happy with.

As a regular reader of this blog you may have noticed, that within the last months, a lot of change happened. I got tired of maintaining a full-blown CMS, called WordPress, just to publish my blog posts. Yes, there is a huge community around WordPress and a lot of great plugins available, but on the other hand, there is a huge attack surface to maintain. I was looking for a more efficient setup to meet my requirements. After a few experiments, this site is now built with Hugo, Cloudflare and Love.

I have started my journey with a small set of design goals:

  • Improve page speed
  • Preserve permalinks from WordPress
  • Less maintenance overhead
  • Less attack surface

As things stand today, I can consider all my goals achieved.

Setup

After a few experiments, I was pretty sure that a static page generated with Hugo was the right solution. Since I have always been very satisfied with Cloudflare’s services, Cloudflare Pages seemed to be a good choice for hosting. Further tests confirmed my assumption.

Hugo

The first thing I looked for was the right theme. I like clean styles and may be one of the last IT people who prefers bright colors. I ended up with the Stack Theme and was quite happy with the Hugo Theme Stack Starter Template which gave me a ramp-on into the Hugo universe.

GitHub Repository

I use a private GitHub repository generated from the Hugo Theme Stack Starter Template. The reason the repo is set to private is simply because my post drafts tend to be very messy.

Both workflows included in the template are quite useful. The Update-Theme workflow is almost identical in use. After some minor tweaking, the Deploy workflow also fits my needs but is now disabled and I use a different method for deployment.

Deploy workflow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
name: Deploy to Github Pages

on:
    push:
        branches: [master]


jobs:
    deploy:
        runs-on: ubuntu-22.04
        concurrency:
            group: ${{ github.workflow }}-${{ github.ref }}
        steps:
            - uses: actions/checkout@v4
              with:
                submodules: true  # Fetch Hugo themes (true OR recursive)
                fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

            - uses: actions/cache@v4
              with:
                path: /home/runner/.cache/hugo_cache    # <-- with hugo version v0.116.0 and above
                # path: /tmp/hugo_cache                 # <-- with hugo version < v0.116.0
                key: ${{ runner.os }}-hugomod-${{ hashFiles('**/go.sum') }}
                restore-keys: |
                    ${{ runner.os }}-hugomod-                    

            - name: Setup Hugo
              uses: peaceiris/actions-hugo@v3
              with:
                hugo-version: 0.128.2
                extended: true

            - name: Build
              run: hugo --minify

            - name: Publish to Cloudflare Pages
              uses: cloudflare/pages-action@v1
              with:
                  apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
                  accountId: 0a3cf1a2ab3e0a097b7d7546d9a4cccd
                  projectName: static-mycloudrevolution
                  directory: public
                  wranglerVersion: "3"

Update-Theme workflow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
name: Update theme

# Controls when the workflow will run
on:
  schedule:
    # Update theme automatically everyday at 00:00 UTC
    - cron: "0 0 * * *"
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

jobs:
  update-theme:
    runs-on: ubuntu-latest

    permissions:
      # Give the default GITHUB_TOKEN write permission to commit and push the
      # added or changed files to the repository.
      contents: write
    
    steps:
      - uses: actions/checkout@v4

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 0.128.2
          extended: true

      - name: Update theme
        run: hugo mod get -u github.com/CaiJimmy/hugo-theme-stack/v3

      - name: Tidy go.mod, go.sum
        run: hugo mod tidy

      - name: Commit changes
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          commit_message: "CI: Update theme"

Local Environment

Thanks to the great stack in the backend, the local environment can be any markedown editor. I prefer to work on my Ubuntu 24.04 LTS with VSCode as a Markedown Editor / Preview tool. A local Hugo server is very useful, especially for configuration changes or Theme modifications.

To install the extended edition of Hugo on Ubuntu 24.04:

1
sudo snap install hugo

The Hugo snap package is strictly confined. Strictly confined snaps run in complete isolation, up to a minimal access level that’s deemed always safe. The sites you create and build must be located within your home directory, or on removable media.

Cloudflare Pages

With Cloudflare Pages, you can create full-stack applications that are instantly deployed to the Cloudflare global network. Cloudflare Pages are available on all plans.

Cloudflare provides the ability for the Hugo Framework to stage versions in different environments.

Hugo versions as Environment variables

The Page for this Blog is connected to the private GitHub repository mentioned above.

To be notified about project updates I have configured a simple Email Notification. This notification is also triggered when the Hugo Theme is updated by the GitHub workflow schedule mentioned above.

Cloudflare Email Notification

Cloudflare Speed, Caching and Security

A good starting point is enabling the Cloudflare basic features for your website.

Cloudflare basic features

But Cloudflare offers even in the free plan way more Speed, Caching and Security features.

A great example is the “Hotlink Protection” feature, which in my case results in almost 300 blocks per day:

When Cloudflare receives an image request for your site, we check to ensure the request did not originate from visitors on another site. Visitors to your domain will still be able to download and view images.

Technically, this means that Hotlink Protection denies access to requests when the HTTP referer does not include your website domain name (and is not blank).

Hotlink protection has no impact on crawling, but it will prevent the images from being displayed on sites such as Google images, Pinterest, and Facebook.

Source: https://developers.cloudflare.com/waf/tools/scrape-shield/hotlink-protection/

The speed and caching mechanism combined with the static content results in a huge improvement in overall page speed and therefore user experience.

Page Speed

Cloudflare SSL/TLS

Cloudflare’s default offering, a managed universal certificate for mycloudrevolution.com, *.mycloudrevolution.com works pretty well for me.

SSL Labs attest an A+ rating for these settings:

SSL Labs Rating

Deployment

Production branch

Commits (or a pull request merge) to the main branch will automatically trigger deployments to the Production environment.

Note: Using only triggered deployments can be a problem if you plan to schedule posts. Hugo does not build future posts by default, hugo --buildFuture forces Hugo to build future posts, too. For scheduled posts, a GitHub action with a cron schedule might be a better fit.

Preview branch

All commits to non-production (!main) branches will automatically trigger deployments to the Preview environment. For each commit is a preview version available:

Cloudflare commit  preview version

For GitHub Pull Request even a comment with the referencing preview version is triggered:

Cloudflare comment on the Pull Request

WordPress Migration

The most challenging step was migrating my content from WordPress to markdown. None of the standard export functions were satisfying. Finally, I ended up with the wordpress-export-to-markdown tool.

This tool uses a WordPress export file as input to scrape the content of the running site.

The result required only minimal editing and tweaking, such as adding aliases, categories, and tags to the post metadata.

Metadata example with aliases, categories, and tags:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
---
title: "Azure Automation with Ansible"
description: "Ansible with its huge community and broad platform support is one of the tools you should have a look at to address your cloud automation demands."
slug: "Azure Automation with Ansible"
image: images/image-3-1024x299.png
date: "2023-12-19"
categories: 
  - "Ansible"
  - "Azure"
tags: 
  - "IaC"
  - "DevOps"
aliases:
  - "/en/2023/12/19/azure-automation-with-ansible/"
params:
  author: Markus Kraus
---

Summary

The solution of Hugo, Cloudflare and a private GitHub repository is a perfect fit for my needs. As of today, I can say that all my goals have been met, and my annual costs have been dramatically reduced to less than $10.

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy