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:
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user