M2: content collections — case studies, blog, RSS, tags, sitemap

Projects + blog as schema-validated content collections; structured case
studies (problem/design/outcome), blog with tag pages, reading time, RSS
feed (drafts excluded), sitemap, and Shiki dual-theme code highlighting.
This commit is contained in:
2026-06-17 16:56:46 +10:00
parent 720d579386
commit 22f482d89a
26 changed files with 1139 additions and 105 deletions
+11
View File
@@ -0,0 +1,11 @@
---
title: "Draft: notes on air-gapped registry mirroring"
date: 2026-06-17
summary: "Work in progress — this draft should never appear in the production build or the RSS feed."
tags: ["draft", "registry"]
draft: true
---
This post is intentionally marked `draft: true` to verify that drafts are excluded from the
production build and the RSS feed. If you can read this on the live site, the draft filter is
broken.
@@ -0,0 +1,52 @@
---
title: "Init-gating GPU readiness on Kubernetes"
date: 2026-06-10
summary: "The single highest-leverage reliability fix for edge GPU workloads: never let an inference pod schedule before the GPU is actually ready."
tags: ["kubernetes", "gpu", "edge", "reliability"]
---
The most common way a GPU workload fails at the edge isn't the model, the driver, or the
network. It's timing. Kubernetes is eager — it will happily schedule your inference pod the
moment a node is `Ready`, which is often *before* the NVIDIA device plugin has advertised
`nvidia.com/gpu`. The pod starts, can't see a GPU, crash-loops, and now your rollout is
poisoned across the fleet.
The fix is to make readiness explicit. Don't trust node-`Ready`; gate on the GPU.
## Gate the schedule, not just the start
A resource request is the first line — a pod that *requests* a GPU won't schedule until the
plugin advertises capacity:
```yaml
resources:
limits:
nvidia.com/gpu: 1
```
But on a single-GPU edge node that's recovering from a reboot, you still want a hard check
before the workload does anything expensive. An init container that blocks until the device
is real keeps the main container honest:
```bash
#!/usr/bin/env bash
set -euo pipefail
# Block until the GPU is visible AND healthy, or fail loudly after a bound.
for i in $(seq 1 30); do
if nvidia-smi -L | grep -q '^GPU 0'; then
echo "GPU ready"; exit 0
fi
echo "waiting for GPU ($i/30)"; sleep 5
done
echo "GPU never became ready" >&2
exit 1
```
## Why this is the win
Once readiness is gated, the whole class of "pod started before the GPU" failures disappears
— and it disappears *the same way on every node*. That consistency is the real prize at the
edge, where no one is standing next to the box to nurse a bad rollout.
The principle generalises: at the edge, **design the dependency, don't hope for it**. The GPU
is just the first dependency worth gating; egress paths and model artifacts are next.
+40
View File
@@ -0,0 +1,40 @@
---
title: "Shipping this site: GitOps from a homelab to the public internet"
date: 2026-06-15
summary: "How this portfolio is built and served — Astro to a container image, a self-hosted Gitea registry, ArgoCD, and a Cloudflare Tunnel — with security as acceptance criteria, not polish."
tags: ["gitops", "astro", "homelab", "security"]
---
This site is a static Astro build, but how it gets to you is the interesting part. It's
served from my homelab Kubernetes cluster over a Cloudflare Tunnel, deployed the same way I'd
ship anything else: as an immutable image, pinned by digest, reconciled by GitOps.
## The pipeline
1. The site is built and baked into a hardened `nginx-unprivileged` image.
2. The image is pushed to a **self-hosted public Gitea registry** — deliberately separate
from the private instance that holds my infrastructure code.
3. The image digest is pinned in a private `home-ops` repo.
4. **ArgoCD** reconciles that repo onto the cluster.
5. A **Cloudflare Tunnel** exposes exactly one service — this site — outbound-only.
No open ports. No server runtime. No registry credential on the cluster, because the public
package is anonymous-pull and the image holds nothing secret.
## Security as acceptance criteria
The interesting constraint was treating security as a checklist to *pass*, not a vibe:
```text
[x] Static output — no server runtime to attack
[x] Strict CSP, no unsafe-inline / unsafe-eval
[x] Self-hosted fonts — zero third-party requests
[x] No secrets in the client bundle (verified by build-time grep)
[x] Outbound-only tunnel, single hostname, no catch-all
```
## Why bother
Because the site *is* the argument. A platform engineer's portfolio should demonstrate the
discipline it's advertising — and "it's a static page" is no excuse to skip the rigour. The
deployment story is part of the work.