When I started this blog, I did the laziest possible thing: pushed the source to a public GitHub repo, flipped the GitHub Pages switch, pointed blog.diego.dev at d6o.github.io, and forgot about it. It worked. I wrote five posts. I moved on.
Last week I came back to write something new, looked at the browser tab, and realized two things at once. First, I wanted my agents to be able to do anything I needed on the blog, and GitHub kept making that harder than it should be. Second, PaperMod is the most-cloned Hugo theme on the planet, and my blog was visually indistinguishable from countless others. Both had been quietly nagging me for a while. I decided to fix them in one sitting.
This is the story of that afternoon: moving the deploy off GitHub onto my own Gitea instance plus Cloudflare Pages, and then rewriting the theme from scratch.
How it started
The migration didn’t actually start with the blog. It started with my Gitea repos.
I’d been running into too many small frictions automating things against the GitHub API. Token scopes, rate limits, the constant churn of organization policies and API surface. Once I stood up a self-hosted Gitea instance on my own k3s cluster, the automation pain mostly went away. I control the token scopes. I can hand my agents whatever permissions they need without negotiating with anyone. Nothing breaks because someone at GitHub flipped a flag.
That kicked off a trend. More k3s nodes added to the cluster on a desk in my home office. Gitea, Gitea Actions, and one service after another moving off VPS instances and onto the homelab. Within a few months, most of what I run is sitting a few feet from where I’m typing this. (The homelab evolution is its own story and deserves its own post.)
While decommissioning one of those VPS instances, I noticed it was hosting diego.dev behind a Caddy reverse proxy (the same Caddy setup I wrote about before). I migrated the personal site to the homelab. Then I realized the blog wasn’t on the same VPS. It was on GitHub Pages. To centralize, I asked my agent to move the source to Gitea and wire up a Gitea Actions workflow.
The deploy target was the next call. Most of my other domains were already on Cloudflare, so pointing the blog at Cloudflare Pages instead of standing up yet another homelab service just made sense. (Why I ended up on Cloudflare for everything is its own thread.)
The shape
The pattern I use for every static site I run looks like this:
- Source repo on Gitea, named after the bare domain (
blog.diego.dev). - A Cloudflare Pages project, named with dashes because Pages doesn’t allow dots in project names (
blog-diego-dev). - A
.gitea/workflows/deploy.ymlthat builds the site and runswrangler pages deploy publicagainst that Pages project. - A proxied CNAME in the Cloudflare DNS zone pointing the public domain at
<project>.pages.dev. - Two repo secrets,
CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_ID, so the workflow can authenticate.
Migrating the blog meant recreating the source repo in this shape and flipping the existing CNAME. The DNS for diego.dev was already on Cloudflare, so I didn’t need an NS change. Just a PATCH on the existing record’s content field, swapping d6o.github.io for blog-diego-dev.pages.dev.
The migration itself
I asked my agent to do the migration. With its own Gitea token and its own Cloudflare API token, it created the Gitea repo, dropped the Cloudflare secrets into it, created the Cloudflare Pages project, and attached blog.diego.dev as a custom domain. (Cloudflare lets you attach the domain before any code has been pushed. It sits at status initializing until DNS resolves.)
In the local clone, the agent:
- Renamed
origintogithub(kept the GitHub repo as a passive mirror). - Added the Gitea remote as the new
origin. - Replaced
.github/workflows/hugo.yamlwith.gitea/workflows/deploy.yml. - Untracked the committed
public/directory, a legacy from before GitHub Actions started doing the build.
Push to Gitea, watch the workflow run, flip the CNAME content. Within minutes, the Pages custom domain validated and blog.diego.dev was live on the new pipeline. As far as readers were concerned, nothing changed.
One thing did try to stop me along the way.
Hugo 0.145 broke PaperMod
After the first push, the build ran. And immediately failed:
ERROR deprecated: site config key paginate was deprecated in Hugo v0.128.0 and subsequently removed.
ERROR deprecated: .Site.Social was deprecated in Hugo v0.124.0 and subsequently removed.
I had bumped Hugo from 0.128.0 (what the old GitHub Pages workflow used) to 0.145.0 to match the rest of my fleet. That broke three things in the older PaperMod fork I had checked into the repo:
- The top-level
paginateconfig key was removed in favour ofpagination.pagerSize. .Site.Socialwas removed in favour of.Site.Params.Social. PaperMod’s templates had a backward-compat fallback that referenced both, but in 0.145 even referencing the removed key is a hard error.- The
partials/prefix inpartial "partials/templates/_funcs/get-page-images"calls became a hard error.
The fastest path to a working build was to pin Hugo back to 0.128.0. The deeper fix was to upgrade or replace PaperMod entirely. I made a note and shipped the migration with the pin in place. By that evening the blog was deploying through Gitea Actions to Cloudflare Pages, end to end on my own pipeline.
And then, with the migration done, I sat with the result.
The other problem
Looking at the blog with fresh eyes for the first time in months, the visual situation became impossible to ignore. PaperMod is fine. It is also the theme I see on every personal dev blog I land on, and the chrome it adds (top nav with Search/Tags/Archive, share buttons under every post, breadcrumbs, reading-time progress bar, light/dark toggle, social icons) stops earning its space the moment you start writing.
I have a personal site at diego.dev that’s the opposite of that. It’s a single HTML file: a name, a few links separated by pipes, a list of jobs, a small <style> block at the top of <head>. Zero dependencies. It auto-switches between light and dark based on prefers-color-scheme. It’s small enough to make 10kb.club. It’s exactly what I want a personal site to be.
I wanted the blog to feel like the same person made it.
Designing it as a document
The constraint I gave myself was tight: extend diego.dev’s visual language to long-form blog content. Same colors. Same card-on-neutral-background layout. Same auto dark mode. Same instinct about what to leave out.
The blog reads as a document. A single white card centered on the neutral background, 800px wide on the home and 720px on post pages (the readable optimum for long-form prose). All the chrome of the previous theme is gone. The site header is one line: site title plus a strip of links separated by middle dots. That’s it.
I wrote a small custom Hugo theme to do this. No JavaScript anywhere. PaperMod is gone from the repo, replaced with a much smaller pile of files I can read end to end. With the new theme in place, I bumped Hugo from 0.128.0 back up to 0.145.0, the version every other site in my fleet uses.
Final thoughts
By the end of the afternoon, both things that had been quietly bothering me were fixed.
The blog now runs on the same pipeline as everything else I ship: a Gitea repo, a Gitea Actions workflow, a wrangler pages deploy step. Pushing a commit to main rebuilds the site and pushes it through Cloudflare Pages within a minute. My agents have the access they need to do anything on it without me getting in the way.
It also no longer looks like every other PaperMod blog. It looks like diego.dev writing long-form posts, because it is.
If you’re reading this on blog.diego.dev after this post went up, you’re seeing it. The blog you’re looking at right now is the one I just described.