arcane@prod : ~/writeups/self-hosting-on-k3s
online· p99 12ms· 142 rps

// writeups

Self-hosting a portfolio on bare-metal k3s

· 1 min read

This site is served from a single-node k3s cluster on repurposed hardware. There are zero inbound ports: a Cloudflare Tunnel dials out from the box, so nothing is exposed to the internet directly — the origin has no public IP surface at all.

GitOps, not SSH

Deploys are a merge, not a manual step. A push to main triggers a GitHub Actions build that pushes an image to GHCR and pins the tag into a kustomization; ArgoCD on the box reconciles the cluster to match git:

push main → CI build → GHCR image → pin kustomization [skip ci] → ArgoCD sync → rolling update

The frontend web app syncs automatically via ArgoCD; the Go core, being deploy-sensitive, is applied by the operator after CI pins it. Caddy sits in front as the in-cluster reverse proxy with flush_interval -1 so Server-Sent Events stream through unbuffered.

Observability on display

Prometheus, Grafana and Loki run in an observability namespace and feed the public /system page — real request rate, p99 latency, resource usage, a deploy feed wired to the CI pipeline, and a redacted tail of the platform's own logs. The metrics you see are the box serving you the page.

Everything is in the repo

  • infra/k8s/ — namespaces, deployments, services, NetworkPolicies, RBAC.
  • infra/argocd/ — the GitOps applications.
  • infra/cloudflared/ — the tunnel config.
  • .github/workflows/ — the build + deploy pipelines.

The /infra page renders that same IaC on display — the manifests, the workflow, the tunnel — annotated. It's a portfolio that documents its own provisioning.

← all writeups