№ 014 · 2026-08-27 · shipped · docker-prod-01

The blog that deployed itself

claudefastapidockerdnsautomationmeta

The most home-lab sentence I’ll ever write: before building this blog, we built the ability to build this blog.

The skill

The idea was to teach Claude our standard ship-a-website move once, so it never has to be re-derived. The recipe, codified as a reusable skill:

  1. New project folder, new private repo on our self-hosted Forgejo
  2. FastAPI app, Bootstrap-or-less on the front, our house conventions baked in
  3. Dockerfile + compose file matching how every other container here runs
  4. Probe the server for the next free port — no hardcoded guesses
  5. Deploy to the prod Docker host, smoke-test on the LAN first
  6. Create the public subdomain and route traffic to the new container, through the secure tunnel out of the home lab (nothing here is exposed directly to the internet)
  7. Validate from the outside — an entry isn’t “shipped” until the public URL answers correctly from the real internet, not just the LAN

Then: “let’s do a blog at blog.stancs.com.” One sitting later, this site existed — dark log-style index, article pages, a tiny JSON API so Claude can check what’s published, no database, every article a markdown file in git.

The bugs, because there are always bugs

A screenshot is not a credential. During setup, Claude read an API token off the screen and typed what it saw. O versus 0 in a monospace font — invalid token. The fix was almost funny: use the copy button like a person, then read the clipboard. Eyes lie; clipboards don’t.

DNS negative caching. Right after creating the new subdomain, validation kept failing with “no such domain” — because we’d asked about the name seconds before it existed, and the local resolver dutifully cached the “no.” The validator now asks a public resolver directly and connects to the returned address explicitly, immune to its own machine’s stale memory.

The CDN cached our old look. After restyling the site, the origin served the new stylesheet while the edge kept confidently serving the old one. Asset URLs now carry a version stamp that changes on every rebuild, so a deploy actually looks deployed.

macOS tar has opinions. Shipping code from a Mac scattered ._* metadata files into the container, and the article loader tried to parse one as an article. Two-line fix, mild embarrassment, standard Tuesday.

Why bother

Because the marginal cost of the next site is now one sentence. And because every one of those bugs is permanently fixed in the pipeline, not in somebody’s memory. The lab doesn’t just accumulate containers — it accumulates competence.