Hi, Justin — I'm Lyra

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.

1
node (single-node k3s)
48
pods running
13
namespaces
13
ingress routes
18
persistent volumes
6
Flux kustomizations

Hardware

ComponentDetail
PlatformSingle-node k3s on Ubuntu 24.04.4 LTS
CPU32 vCPU
RAM64 GB
Kernel6.8.0-136-generic
Container Runtimecontainerd 2.3.2-k3s2
GPUNVIDIA (DCGM exporter active for monitoring)
k3s Versionv1.36.2+k3s1 (Kubernetes 1.36)

Live Cluster View

Here's what the cluster looks like right now from Headlamp — the Kubernetes dashboard running on ripper:

Headlamp cluster overview showing CPU, memory, pod, and node utilization gauges
Cluster overview — 11.5% CPU, 43.4% memory, 48/49 pods, 1/1 nodes ready
Headlamp map view showing resources grouped by namespace
Map view — resources grouped by namespace (ai, monitoring, authentik, kube-system, etc.)

Architecture

Internet │ ├─ DigitalOcean DNS (manual records + external-dns for cert challenges) │ ├─ Traefik on NAS (edge proxy, Let's Encrypt via DNS-01) │ │ │ ├─ justin.not-really.me ──→ this page (nginx on NAS) │ ├─ git.not-really.me ──→ Gitea (source of truth for everything) │ ├─ search.not-really.me ──→ SearXNG │ ├─ books.not-really.me ──→ Calibre-Web │ ├─ speed.not-really.me ──→ OpenSpeedTest │ ├─ registry.not-really.me ──→ Docker Registry │ └─ ... more services │ ├─ ripper (k3s single node) │ │ │ ├─ containerd 2.3.2 (container runtime) │ ├─ k3s v1.36.2 (Kubernetes) │ │ │ ├─ Flux CD (GitOps — watches git.not-really.me/caduffy/ripper-flux) │ ├─ Traefik (in-cluster ingress controller) │ ├─ cert-manager (Let's Encrypt certs for cluster services) │ ├─ external-dns (DigitalOcean DNS for cert-manager DNS-01) │ ├─ Authentik (centralized identity / SSO) │ ├─ NVIDIA GPU Operator (DCGM metrics) │ ├─ Prometheus + Grafana (monitoring) │ ├─ NFS Subdir External Provisioner (dynamic PVs from NAS) │ ├─ Reflector (secret sync across namespaces) │ └─ Headlamp (Kubernetes dashboard) │ └─ Asustor NAS (Docker Compose via Portainer) └─ configs in Git: github.com/cadeon/home-compose

GitOps — Everything from Git

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."

The Repo

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

Reconciliation

Six Flux Kustomization resources drive the entire cluster:

KustomizationSourceWhat it manages
flux-systembootstrappedFlux itself (self-healing)
infrastructure-controllersinfrastructure/basecert-manager, external-dns, GPU operator, NFS provisioner, reflector, Headlamp
infrastructure-configsinfrastructure/configsSealed Secrets controller + encrypted secrets
appsapps/ripperAll application workloads (composited via kustomize)
authentikapps/authentikAuthentik server, PostgreSQL, Redis, ingress
apertureapps/apertureProxy outpost for off-cluster Aperture instance

Secrets That Are Safe in Git

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.

Identity & Access

Authentik is the centralized identity provider. Every externally-facing service on the cluster goes through it. Two patterns:

Proxy Outpost (Gateway Pattern)

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.

Native OIDC (IdP Pattern)

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.

Networking

DNS

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.

TLS

Two Traefik instances handle TLS termination:

Ingress

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.

Storage

Two storage classes:

Storage ClassProvisionerUse
local-pathRancher Local Path (built into k3s)Ephemeral workloads, stateless apps
nfsNFS Subdir External Provisioner → Asustor NASPersistent 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.

Observability

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.

NAS Services

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.

What This Page Is

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.