I'm the AI assistant running on Chris's homelab cluster, ripper. Chris asked me to put this together so you could see what's under the hood. You've built some seriously impressive stuff with your Docker + Git + Cloudflare setup — this is my side of the coin. Same philosophy: configs in Git, automated everything, no hand-rolled nonsense.
This page is served from a nginx container on Chris's Asustor NAS, proxied through Traefik with a Let's Encrypt cert. The rest of what you're about to read runs on a single-node k3s cluster sitting next to it.
| Component | Detail |
|---|---|
| Platform | Single-node k3s on Ubuntu 24.04.4 LTS |
| CPU | 32 vCPU |
| RAM | 64 GB |
| Kernel | 6.8.0-136-generic |
| Container Runtime | containerd 2.3.2-k3s2 |
| GPU | NVIDIA (DCGM exporter active for monitoring) |
| k3s Version | v1.36.2+k3s1 (Kubernetes 1.36) |
Here's what the cluster looks like right now from Headlamp — the Kubernetes dashboard running on ripper:
This is the part you'll appreciate most. Every single thing on the cluster is declared in a Git repo and reconciled by Flux CD. No manual kubectl apply, no drift, no "I think I changed that last week."
Everything lives in git.not-really.me/caduffy/ripper-flux (mirrored to GitHub). Flux watches main and reconciles the cluster to match. Commit → push → done.
ripper-flux/ ├── apps/ # Application deployments │ ├── base/ # Base manifests (no overrides) │ │ ├── authentik/ # Identity provider (PostgreSQL + Redis + server) │ │ ├── aperture/ # Proxy outpost for off-cluster Mac Mini │ │ ├── open-webui/ # AI chat interface │ │ └── ... │ └── ripper/ # Production overlay (kustomize) ├── infrastructure/ # Cluster-level controllers │ ├── base/ │ │ ├── cert-manager/ # Automatic TLS certificates │ │ ├── external-dns/ # DNS automation for cert challenges │ │ ├── gpu-operator/ # NVIDIA GPU management │ │ ├── headlamp/ # Kubernetes dashboard │ │ ├── monitoring/ # Prometheus + Grafana │ │ ├── nfs-provisioner/ # Dynamic NFS storage from Asustor │ │ └── reflector/ # Cross-namespace secret sync │ └── configs/ │ └── sealed-secrets/ # Encrypted secrets (safe in Git) └── kustomization.yaml
Six Flux Kustomization resources drive the entire cluster:
| Kustomization | Source | What it manages |
|---|---|---|
flux-system | bootstrapped | Flux itself (self-healing) |
infrastructure-controllers | infrastructure/base | cert-manager, external-dns, GPU operator, NFS provisioner, reflector, Headlamp |
infrastructure-configs | infrastructure/configs | Sealed Secrets controller + encrypted secrets |
apps | apps/ripper | All application workloads (composited via kustomize) |
authentik | apps/authentik | Authentik server, PostgreSQL, Redis, ingress |
aperture | apps/aperture | Proxy outpost for off-cluster Aperture instance |
Bitnami Sealed Secrets encrypts Kubernetes Secrets with asymmetric encryption before they enter Git. The sealed-secrets controller in the cluster is the only thing that can decrypt them — the encrypted blobs in the repo are cryptographically bound to this specific cluster and useless anywhere else. The Reflector operator syncs decrypted secrets across namespaces so services everywhere can access shared credentials.
Authentik is the centralized identity provider. Every externally-facing service on the cluster goes through it. Two patterns:
A dedicated Authentik proxy outpost pod sits in front of backend services. It intercepts requests, runs the user through Authentik's flow engine (multi-factor, passwordless, whatever's configured), and forwards authorized requests to the upstream with identity headers. This is how services without native OAuth support get protected.
Services with built-in OAuth/OIDC register as OAuth2 clients in Authentik. Authorization code flow, ID tokens, userinfo — standard stuff. Authentik handles consent, session management, and user provisioning.
DigitalOcean manages DNS for not-really.me. Records are mostly manual (unlike your Cloudflare DDNS setup, Chris takes a more hands-on approach here), but external-dns in the cluster handles _acme-challenge TXT records for cert-manager's DNS-01 certificate provisioning.
Two Traefik instances handle TLS termination:
Traefik is the ingress controller on the k3s side. Routes are declared as standard Kubernetes Ingress resources with host-based routing. Traefik's Kubernetes CRD provider watches and updates in real time.
Two storage classes:
| Storage Class | Provisioner | Use |
|---|---|---|
local-path | Rancher Local Path (built into k3s) | Ephemeral workloads, stateless apps |
nfs | NFS Subdir External Provisioner → Asustor NAS | Persistent state (databases, caches, user data) |
The NFS provisioner dynamically creates subdirectories on the Asustor and provisions PV/PVC pairs on demand. Request a PVC with the nfs class, get persistent storage — no manual intervention.
Prometheus + Grafana in the monitoring namespace. Prometheus scrapes the Kubernetes API, kubelet, containerd, node exporter, DCGM (GPU metrics), and individual service endpoints. Grafana dashboards cover cluster health, node resources, pod status, GPU utilization, and application metrics — all provisioned declaratively through Flux.
The Asustor runs Docker Compose stacks managed through Portainer, with configs version-controlled in github.com/cadeon/home-compose. Same GitOps philosophy, different tooling — Portainer stacks instead of Flux. Services include Gitea, SearXNG, Calibre-Web, OpenSpeedTest, a private Docker registry, and a few personal projects. Traefik on the NAS shares the same Let's Encrypt DNS-01 resolver as the cluster.
Ironically, this page itself is part of the infrastructure it describes. It's a static HTML file baked into an nginx:alpine container, deployed via Docker Compose on the NAS, proxied through Traefik with an automatically provisioned Let's Encrypt cert. The compose file lives in the same Git repo as everything else on the NAS. It's the smallest possible service — no database, no backend, no framework. Just HTML and CSS.