Skip to content
Cumps.
How This Blog Is Served

How This Blog Is Served

  • Oct 27, 2026

This post eats its own dog food in real time: it’s about the machinery delivering it to you. The blog was the first non-mail service on the platform, built back in February, and the decisions made for it quietly became the template every later service copied.

Choosing boring, again

The platform evaluation came down to Hugo, Ghost, and WriteFreely. Ghost wants a Node.js runtime and a database and roughly 500 MB of RAM to exist. WriteFreely is leaner but still an application plus a database. Hugo is a compiler: Markdown goes in on my workstation, HTML comes out, and the server’s only job is handing files to browsers. No admin panel to breach, no database to back up, no runtime to patch. For a single technical author who lives in git anyway, the web-based editor a dynamic platform offers is a feature I’d pay for in maintenance and never use.

There’s a sovereignty angle too, smaller than jurisdiction but real: the content is a folder of Markdown files in the same repository as the Terraform and Ansible that build the server. Nothing about this blog is trapped anywhere. The fonts are self-hosted WOFF2 files, so no reader ever touches a Google Fonts endpoint by reading my words. No analytics, no trackers; there is no dashboard telling me how many people read this, which is occasionally frustrating and mostly liberating.

The first tenant of apps1

Mail owns mx1 and mx2, and I wanted it to stay that way, so the blog forced the creation of a third compute instance: apps1, the general-purpose application host, provisioned as its own Terraform layer. The blog was its first tenant; Forgejo, Vaultwarden, the dashboard, and half the fleet’s services moved in later, all following the pattern the blog established: an upstream container, a Tailscale sidecar for mesh identity, Traefik in front, a numbered Ansible playbook to deploy it.

The serving container is nginx-unprivileged, the Alpine variant that runs as a non-root user. For static files even nginx is arguably overkill, so it might as well be the version with the smallest possible blast radius.

Deploying is one script: deploy.sh builds the site with Hugo and rsyncs the output to apps1 via Ansible. Publishing a post is a git commit and a script run, which suits the way I already work better than any CMS could.

Hardening a pile of static files

A static site’s attack surface is small, and I hardened it anyway, mostly because the exercise was cheap. A strict Content-Security-Policy with no inline scripts, which had a delayed cost: months later, adding navigation JavaScript meant the handlers had to live in external files because the CSP refused inline ones, and the CSP won the argument. Hidden files are blocked outright, security headers are explicit, and the TLS cipher policy from the fleet’s hardening pass applies here like everywhere else.

The build pipeline got one sharp edge I only discovered while writing this very series. PurgeCSS strips unused styles based on a class inventory from the last build, and pagination markup only renders once there are more than six posts. The stylesheet for pagination was therefore being purged as “unused”, a time bomb set to detonate the week the front page first grew a second page. It got caught and fixed while I was drafting this series, weeks before you would have seen an unstyled list of page numbers. Static sites are simple, but build pipelines are still software.

The loop closes

What I like most about this setup has nothing to do with technology. The posts documenting the platform live in the repository that defines the platform, deploy through the same Ansible that deploys everything else, and are served from infrastructure the posts themselves describe. When I write “the server does X”, the claim and the server share a git history. Documentation this close to the thing it documents has a much harder time lying.

Next week the series enters the service era: the dashboard that gave my family one URL to find everything.

About Me Author

My name is

David Cumps

A passionate geek, always curious, always learning.

You May Also Like