Homelab: the server behind this site
Live dashboard
Golden signals, visitors, saturation and deployments, refreshed every 30 seconds.
Connecting to the cluster…
How a commit reaches production
Every push to main is tested, built and deployed without anyone touching the server. The tags shown are what is running right now.
Push
Commit to main on an app repo
Test
GitHub Actions: ruff, pytest, typecheck
Build
Buildx image with SBOM + provenance
Publish
GHCR, immutable sha tag
GitOps
CI pins the tag in the Helm repo
Argo CD
Syncs the change
k3s
Rolling update at home
my-portfolio
Next.js · typical run about 1.5 min
- Typecheck & build
- Push to GHCR
- Pin tag
- Running now
- –
- Last deployed
- –
homelab-api
FastAPI · typical run about 1 min
- Lint
- Test
- Build
- Push to GHCR
- Pin tag
- Running now
- –
- Last deployed
- –
Immutable images
Every deploy is a sha-<commit> tag, so what runs maps to exactly one commit.
Rollback is a git revert
Reverting the tag commit in the GitOps repo rolls the cluster back.
Drift heals itself
Argo CD self-heal undoes any change made to the cluster by hand.
PRs never deploy
Pull requests build and test, but only main reaches production.
What is deployed right now
Live deployment status appears when the homelab is online.
How this page reached you
Your request came through Cloudflare into the k3s cluster in my home, without any open ports. Every service it touched is shown below.
Healthy in Argo CD PlannedScroll sideways to see the whole diagram.
Security
A public website served from my home network, so the design assumes the internet is hostile. Every control below lives in the repos, and its status is stated honestly.
Open ports at home
0
All traffic arrives through an outbound tunnel
Controls
20 of 25 enforced
2 partial · 3 planned, listed honestly below
Deployed images
CVE-gated
Nothing ships with a fixable critical vulnerability
Attack surface
Reachable from the internet, through Cloudflare
Site pages
Static and server-rendered HTML
/api/status, /api/metrics
Fixed, cached, sanitised JSON
/stats/*
Analytics script and event proxy, 16 KB body cap
Never reachable from the internet
- Prometheus
- Grafana
- Argo CD
- Umami admin
- Postgres
- Kubernetes API
- homelab-api
Inside the cluster, NetworkPolicies decide which of these may talk to each other.
Security headers, checked live
Checking this page's response…
- Content-Security-PolicyLimits where scripts and data can load fromchecking
- Strict-Transport-SecurityBrowsers only ever use HTTPSchecking
- X-Frame-OptionsBlocks clickjacking in frameschecking
- X-Content-Type-OptionsStops MIME-type sniffingchecking
- Referrer-PolicyKeeps full URLs out of other sites' logschecking
- Permissions-PolicyCamera, mic and location are offchecking
- Cross-Origin-Opener-PolicyIsolates this tab from other originschecking
Your browser fetched this page again and read the response headers; nothing here is hard-coded.
Controls by layer
Layer 1
Edge
- TLS and DDoS protection at CloudflareEnforced
- Rate limiting on /api routesPlanned
Layer 2
Network
- Zero open ports on the home routerThe tunnel only dials outEnforced
- Default-deny NetworkPoliciesEvery app; the database is inbound-onlyEnforced
- Tunnel cannot reach the home LANEgress limited to public IPsEnforced
- Admin tools never exposedGrafana, Argo CD, Umami adminEnforced
Layer 3
Identity
- Read-only ServiceAccount for deploy statusget/list on Applications onlyEnforced
- Read-only deploy key for Argo CDEnforced
- One scoped write key per CI pipelineEnforced
- Analytics read through a share linkNot an admin loginEnforced
Layer 4
Workload
- Pod Security Standard: restrictedNon-root, no privilege escalationEnforced
- All Linux capabilities droppedEnforced
- Read-only root filesystemsSite, API and tunnel; not Umami or PostgresPartial
- CPU and memory limitsEnforced
Layer 5
Supply chain
- CVE gate: build fails on fixable criticalsTrivy, before anything is pushedEnforced
- SBOM and build provenanceEnforced
- Immutable sha-<commit> image tagsEnforced
- Weekly dependency updatesDependabotEnforced
- Third-party actions pinned to commitsScanner pinned; others by versionPartial
- Signed imagesPlanned
Layer 6
Data
- Secrets in git only as SealedSecretsEnforced
- Fixed queries; no visitor input reaches PrometheusEnforced
- Forged analytics events filteredKnown paths and country codes onlyEnforced
- Strict security headers and CSPChecked live aboveEnforced
- Off-site database backupsPlanned
Caught by the pipeline
When I added the CVE gate, it flagged a critical vulnerability in the Next.js version this site was running (CVE-2026-75604). I upgraded to the fixed release, confirmed the scan passed, and shipped it the same day. Since then, a build with a fixable critical vulnerability cannot reach production.
Disaster recovery
If this machine died tonight, everything comes back from git with one script and two keys from my password manager.
Rebuild from a blank machine
- 1
Install k3s
curl -sfL https://get.k3s.io | sh -
- 2
Clone the GitOps repo
git clone git@github.com:22taran/homelab.git
- 3
Run the bootstrap
./bootstrap/bootstrap.sh sealed-secrets-key.yaml homelab_argocd
The script installs Argo CD, restores the decryption key and applies one root app. Argo CD then recreates every component in order, secrets included, with no further manual steps.
Kept in git
- Every Helm chart and Argo CD Application
- All secrets, encrypted
- The bootstrap script and runbook
Kept offline
- The Sealed Secrets decryption key
- The read-only deploy key Argo CD uses
- That is all; two files in a password manager
While it is down
- Vercel keeps a copy of the same commit to serve the site
- That copy shows the homelab as offline instead of stale numbers
- Analytics data has nightly database dumps