copy pass: straighten quotes and arrows, thin the dash asides, drop the app-count caption from the constellation
build-and-deploy / build (push) Failing after 13m18s

This commit is contained in:
2026-08-17 23:12:42 +10:00
parent 78e8740408
commit 3cfd59a464
23 changed files with 257 additions and 271 deletions
+7 -7
View File
@@ -1,7 +1,7 @@
---
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."
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"]
hero: "/blog/shipping-this-site.webp"
heroAlt: "A glowing data container travels a luminous rail from a small server rack toward a softly glowing globe of Earth, wrapped by a reconcile loop."
@@ -10,16 +10,16 @@ heroAlt: "A glowing data container travels a luminous rail from a small server r
This site is a static Astro build, and that's the least interesting thing about it. What matters
is how it reaches you: served from my homelab Kubernetes cluster over a Cloudflare Tunnel, shipped
the way I'd ship anything I actually cared about an immutable image, pinned by digest, reconciled
the way I'd ship anything I actually cared about - an immutable image, pinned by digest, reconciled
by GitOps. No special case for "it's just a website."
## The pipeline
The build is baked into a hardened `nginx-unprivileged` image and pushed to a **self-hosted public
Gitea registry** deliberately a separate instance from the private one holding my infrastructure
Gitea registry**, deliberately a separate instance from the private one holding my infrastructure
code, so the public artifact and the private estate never share a trust boundary. The image digest
is then pinned in a private `home-ops` repo, **ArgoCD** reconciles that repo onto the cluster, and
a **Cloudflare Tunnel** exposes exactly one service this site outbound-only.
a **Cloudflare Tunnel** exposes exactly one service, this site, outbound-only.
Follow the chain and notice what's missing. No open ports: the tunnel dials out. No server runtime:
the output is static files behind nginx. No registry credential on the cluster: the public package
@@ -32,9 +32,9 @@ The discipline that made it work was treating security as a checklist to *pass*,
gesture at. The site didn't ship until every box was ticked:
```text
[x] Static output no server runtime to attack
[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] 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
```
@@ -46,7 +46,7 @@ verify, and a failing item blocks the ship. "Pretty secure" isn't a state you ca
## The principle
The site *is* the argument. A platform engineer's portfolio should demonstrate the discipline it
advertises, and "it's a static page" is no excuse to skip the rigour it's the cheapest possible
advertises, and "it's a static page" is no excuse to skip the rigour - it's the cheapest possible
place to practise it. If the pipeline behind a brochure site is immutable, verified, and
zero-trust, that's not overkill. That's the standard, rehearsed where the stakes are low so it
holds where they aren't.