The blog started as a Hugo site running on my home server. Markdown files in, static HTML out, served by Caddy through a Cloudflare Tunnel. It worked well. But the more I thought about it, the less sense it made to keep it there.
Why move at all#
The blog was the only thing on my server that needed to be publicly accessible. Everything else, Immich, Kopia, AdGuard, WireGuard, is internal. Private services behind a VPN, the way I want them.
Having the blog on the same machine meant running a Cloudflare Tunnel to expose it. The tunnel itself isn’t a big deal, it’s outbound-only and doesn’t open any ports, but it’s still a public path into my home network. Every public-facing service is one more thing to think about when something goes wrong.
But the real reason was simpler than security. The blog content lived on the server, and editing markdown through SSH in nano is not a great experience. It works, but it’s not how you want to write blog posts. I wanted to edit my posts in a proper editor on my laptop, or even from my phone, and push them when they’re ready. That’s it.
The new setup#
The blog now lives in a GitHub repository. When I push to the main branch, a GitHub Actions workflow builds the Hugo site and deploys it to Cloudflare Pages. The whole pipeline:
- Push markdown to GitHub
- GitHub Actions checks out the repo, runs
hugo --minify - The built site gets deployed to Cloudflare Pages via
wrangler - Cloudflare serves it from their edge network worldwide
The workflow file is about 30 lines:
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: "0.157.0"
extended: true
- name: Build
run: hugo --minify
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy public --project-name=rudysamTwo secrets in the repo, a Cloudflare API token and account ID. That’s the entire infrastructure.
What I removed from the server#
Once the blog was running on Cloudflare Pages, I could clean up the server:
- Deleted
/opt/docker/hugo/— the Hugo source files, themes, build output, rebuild script. All gone. - Removed the Caddy site block — no more
blog.rudysam.compointing to static files on disk. - Removed Ghost and Umami — Ghost was the original blog platform I replaced with Hugo. Umami was self-hosted analytics. Both were still running, using resources for nothing. Stopped the containers, deleted the directories.
- Updated the Cloudflare Tunnel — the blog no longer routes through the tunnel. That’s one less public service exposed through my home network.
The tunnel now only handles Immich share links, which is all it ever needed to do.
DNS and local network#
One thing I had to sort out was DNS. On my home network, AdGuard rewrites *.rudysam.com to the server’s local IP. That was fine when Caddy served the blog locally, but now the blog lives on Cloudflare’s network.
The fix was simple: add a specific DNS rewrite for blog.rudysam.com pointing to rudysam.pages.dev. In AdGuard, specific entries override wildcards. So on my home network, the blog now resolves to Cloudflare just like it does everywhere else.
What’s better#
The workflow. This was the whole point. Write a post, commit, push. That’s it. No SSH, no copying files to a server, no rebuild scripts. Fix a typo from my phone if I want to. The content lives in a Git repo, and pushing to main is publishing. It’s the simplest workflow I could ask for.
Reliability. My server could go down, lose power, need a reboot for updates. None of that affects the blog anymore. It’s on Cloudflare’s infrastructure.
Speed. Cloudflare Pages serves from edge nodes worldwide. My home server is a single Dell laptop in one location. A nice bonus, but not why I moved.
Less attack surface. No Cloudflare Tunnel path to the blog, no static files to serve, no Caddy block to maintain. The server does less, which means less can go wrong.
Analytics. I replaced self-hosted Umami with Cloudflare Web Analytics. One line of JavaScript, no database, no container. It just works.
What I learned#
The instinct with a home server is to self-host everything. That’s the whole point, right? Own your data, run your services, keep control.
But “self-host everything” isn’t actually the goal. The goal is to have things work well, be reliable, and stay under your control. A static blog on Cloudflare Pages still uses my domain, my content, my workflow. I can move it anywhere. The markdown files are in a Git repo I own. Nothing is locked in.
Sometimes the best thing you can do for your home server is take things off it. Every service you remove is one less thing to maintain, monitor, back up, and secure. The server should run what benefits from being local: photos, backups, DNS, VPN. A public blog that serves static files to the internet is better suited to infrastructure that was built for exactly that.
The server is lighter now. Four fewer containers, less disk usage, a simpler Caddy config, a narrower tunnel. And the blog is faster and more reliable than it ever was running from my living room.
