I’ve been reading more blog posts recently and decided I should join in. In this post, I’ll walk you through setting up a personal blog using Hugo and Cloudflare Pages. This is designed to get me up and running quickly. This isn’t meant to be a guide but just a log of what I’ve done.
Hugo setup
Do the base init as per the quick start.
brew install hugo
hugo new site .
git init
git submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
echo "theme = 'PaperMod'" > hugo.toml
Trying out Cursor, next ask to do some admin
- create
.gitignore
- add
robots.txt
- add RSS (just a hugo.toml setting)
- create a blank
content/about.md
We’re now basically ready to go, we just need to customise the about page and config with details specific to my site.
Hosting
So arguably that’s the easy bit, now how do we do the hosting which always feels painful.
As I said earlier I’m using Cloudflare Pages building directly from GitLab.
We can do most of this in Terraform.
First we need to manually link the Cloudflare and GitLab accounts. To do that you need to manually start creating a new Cloudflare page from GitLab, accept the auth link and cancel the cloudflare page creation.
# Some admin which should probably be a variable
locals {
cloudflare_account_id = "<REDACTED>"
hugo_version = "0.147.1"
}
# Create the gitlab project with the default options I like to use
resource "gitlab_project" "blog" {
name = "blog"
description = "Personal blog for danwainwright.com"
visibility_level = "private"
default_branch = "main"
wiki_enabled = false
auto_devops_enabled = false
container_registry_access_level = "disabled"
packages_enabled = true
snippets_enabled = false
security_and_compliance_access_level = "disabled"
builds_access_level = "private"
environments_access_level = "disabled"
feature_flags_access_level = "disabled"
forking_access_level = "disabled"
infrastructure_access_level = "disabled"
model_experiments_access_level = "disabled"
model_registry_access_level = "disabled"
monitor_access_level = "disabled"
pages_access_level = "disabled"
releases_access_level = "disabled"
requirements_access_level = "disabled"
snippets_access_level = "disabled"
analytics_access_level = "disabled"
}
# The pages project itself linked to the gitlab repo
# This manages the full build process
resource "cloudflare_pages_project" "blog" {
account_id = local.cloudflare_account_id
name = "blog"
production_branch = gitlab_project.blog.default_branch
source {
type = "gitlab"
config {
owner = data.gitlab_current_user.user.username
repo_name = gitlab_project.blog.name
production_branch = gitlab_project.blog.default_branch
}
}
build_config {
build_command = "hugo -b $CF_PAGES_URL"
destination_dir = "public"
root_dir = ""
}
deployment_configs {
preview {
environment_variables = {
HUGO_VERSION = local.hugo_version
}
}
production {
environment_variables = {
HUGO_VERSION = local.hugo_version
}
}
}
}
# Link the page to the domain using some route53 setup I already have
resource "cloudflare_pages_domain" "blog" {
account_id = local.cloudflare_account_id
project_name = cloudflare_pages_project.blog.name
domain = "www.${aws_route53_zone.primary.name}"
}
# Route53 CNAME to the cloudflare domain
resource "aws_route53_record" "blog" {
zone_id = aws_route53_zone.primary.zone_id
name = "www"
type = "CNAME"
ttl = 60
records = [ cloudflare_pages_project.blog.subdomain ]
}
And like that we’re up and running.
Testing
curl -I https://www.danwainwright.com | head -n 1
HTTP/2 200
RSS as it’s the best way to consume a blog.
curl -I https://www.danwainwright.com/index.xml | head -n 1
HTTP/2 200
Issues
The Cloudflare Terraform provider is broken for creating pages from GitLab accounts currently, so we’ve pinned against version 4 (issues/18970).
I’ve been unable to get the root of the domain to redirect to www.
RRSet of type CNAME with DNS name X. is not permitted at apex in zone Y.
It sounds like this is
part of the RFC
so you’re meant to use a AWS Route53 Alias
to solve but I can’t do that as cloudflare requires a CNAME
. The fix might be to move away from Route53.
For now this is not solved.
EDIT: I’ve now solved this with
resource "aws_s3_bucket" "root" {
bucket = aws_route53_zone.primary.name
}
resource "aws_s3_bucket_website_configuration" "root" {
bucket = aws_s3_bucket.root.id
redirect_all_requests_to {
host_name = "www.${aws_route53_zone.primary.name}"
protocol = "https"
}
}
resource "aws_s3_bucket_public_access_block" "root" {
bucket = aws_s3_bucket.root.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_route53_record" "root" {
zone_id = aws_route53_zone.primary.zone_id
name = ""
type = "A"
alias {
name = aws_s3_bucket_website_configuration.root.website_domain
zone_id = aws_s3_bucket.root.hosted_zone_id
evaluate_target_health = false
}
}
resource "aws_route53_record" "root_aaaa" {
zone_id = aws_route53_zone.primary.zone_id
name = ""
type = "AAAA"
alias {
name = aws_s3_bucket_website_configuration.root.website_domain
zone_id = aws_s3_bucket.root.hosted_zone_id
evaluate_target_health = false
}
}
Add Content
Now the hardest part, what to write about. We’re starting with an about the blog because why not.
hugo new posts/the-blog.md
That gives us an empty file to write away in.
What’s next
Now I have the blog and a first post let’s see what’s next. I’m not expecting to have lots to blog about but hopefully just having the site up and running gives me the push to write something from time to time.