Building this blog
Why this journal is a folder of markdown files and one build script, with no framework, no database and no dependencies.
This site is where I log what I'm building and what I'm chasing: homelab projects, certification journeys, endurance training, all of it. Before any of that could go up, the blog itself had to exist. So the first entry is about the blog itself.
The constraint#
Everything on this domain follows the same rule: minimal, self-hosted, and understandable end to end. The tracker runs on a VPS I manage myself. The home page is a single HTML file. A blog that pulled in a framework, a headless CMS, and three hundred npm packages to render text would have broken the theme of the whole place.
So the requirements were short: posts are plain markdown files, the output is plain HTML that any web server can serve, and the build must not depend on anything I didn't write.
How it works#
The whole pipeline is one folder and one script:
Blog/
posts/ <- one .md file per post, front matter on top
assets/ <- theme.css (shared with the whole domain) + blog.css
admin/ <- the graphical editor, config.yml and a vendored CMS bundle
build.mjs <- ~1400 lines of Node, zero dependencies
dist/ <- the generated site - what actually gets served
Running node build.mjs reads every markdown file, converts it with a small purpose-built parser, and writes out the index, one page per post, an RSS feed, and a sitemap. There is nothing to install. If Node exists, the blog builds.
The design tokens, the matte black and the one red accent, aren't defined here at all. They live in a shared stylesheet used by every site on this domain, so the journal automatically looks like the tracker, the portfolio, and the home page.
Posting without a terminal#
The pipeline above never required the person writing a post to be comfortable in a terminal, it just happened to be the only way in for a while. In October a graphical editor got added: Decap CMS, reachable at /admin/ behind a second login on top of GitHub's own. It changes nothing about the model above. A post made in the editor is still a markdown file with the same front matter, committed to the same repository build.mjs already reads. There is no new database and no second copy of anything to keep in sync.
What the editor actually needed was somewhere to log in. GitHub's OAuth token exchange has no CORS support, so a small self-hosted bridge handles that one step, and a webhook on the same service rebuilds and republishes the site on its own after every commit. The admin page itself is just another file this folder serves, vendored rather than pulled from a CDN at request time, so the rest of the site's security headers stay exactly as strict as they were before the editor existed.
What "journeys" are#
Some things worth writing about aren't single posts. They're long arcs with a finish line. Training for an Ironman. Working through a certification. Those get a series tag in the front matter, and the build groups them into Journeys: multi-part logs with their own navigation and an "in progress" badge that flips to "complete" when the goal is done.
That's the part I care about most. A write-up published after the fact is tidy but dishonest, because it hides every wrong turn. Logging in parts, while it's happening, keeps the record honest.
What's coming#
The queue, in no particular order:
- Road to Ironman, from a standing start
- PADI Open Water certification
- Infrastructure write-ups as this domain grows
If any of that sounds interesting, there's an RSS feed. There is no newsletter and no comments section, and nothing here tracks you.