Terminal output: talosctl version and kubectl get nodes showing the three Talos cluster nodes

~/homelab > whoami

I run a small production-grade homelab: a three-node Talos Kubernetes cluster on bare-metal Lenovo M700s, fronted by a Debian server that handles NAT, Tailscale subnet routing, and a few services that do not belong in Kubernetes. That server also runs the NVR stack (go2rtc, Frigate, and a Coral USB TPU for on-device object detection), fed by PoE cameras on the same wired LAN as the cluster, isolated from the house WiFi network.

The cluster hosts Git, task tracking, a self-hosted social scheduler, a workflow engine, an object store, and a personal agent stack. Small enough that I understand every layer. Large enough that real failures happen, which is the point.

What I run, and why →

~/homelab > topology

flowchart TB Internet([Internet]):::external CF[Cloudflare DNS]:::external Tailnet([Tailscale Tailnet]):::external Cameras[("PoE Cameras")]:::external subgraph DebianHost["Debian Server (Lenovo M700)"] direction TB DebianEdge["NAT • Subnet Router"]:::edge subgraph NVR["NVR Stack"] direction LR Go2rtc["go2rtc
RTSP proxy"]:::app Frigate["Frigate"]:::app Coral[("Coral USB TPU
object detection")]:::platform end Go2rtc --> Frigate Frigate -.-> Coral end subgraph Cluster["Talos Cluster (3x Lenovo M700)"] direction TB GW["Cilium Gateway API
L2 LoadBalancer Pool"]:::platform subgraph Platform["Platform Layer"] direction LR Longhorn[(Longhorn
Storage)]:::platform Cert[cert-manager]:::platform ExtDNS[ExternalDNS]:::platform Prom[Prometheus
+ Grafana]:::platform end subgraph Public["Public Services"] direction LR Forgejo[Forgejo]:::app Vikunja[Vikunja]:::app Postiz[Postiz Stack]:::app Windmill[Windmill]:::app MinIO[MinIO]:::app end subgraph VClusters["vclusters"] direction LR subgraph Agent["Agent Stack"] Hermes[Hermes Agent]:::agent Synapse[Synapse Matrix]:::agent end subgraph Bench["Bench Sandbox"] Evals[Evaluation Sandboxes]:::sandbox Spare[Spare Sandboxes]:::sandbox end end GW --> Public GW --> Agent Public -.-> Longhorn Agent -.-> Longhorn Bench -.-> Longhorn end Internet --> CF CF --> GW Internet -.->|Wireguard| Tailnet Tailnet --> DebianEdge DebianEdge --> Cluster Cameras --> Go2rtc classDef external stroke:#64748b classDef edge stroke:#fbbf24 classDef platform stroke:#4ade80 classDef app stroke:#60a5fa classDef agent stroke:#a78bfa classDef sandbox stroke:#94a3b8 classDef vclusterGroup stroke:#a78bfa,stroke-dasharray:4 4 class VClusters vclusterGroup

~/homelab > vclusters

The two vclusters in the topology above are what happens to be running today, not a fixed part of the stack. vcluster lets me spin a fresh control plane on the host cluster in a few minutes whenever I need one: a benchmark rig, an upgrade rehearsal, a sketchy experiment that deserves its own blast radius. Each one gets its own API server and CRDs without sharing state with the rest of the cluster. When the work is done I delete the vcluster and the host is left clean.

~/homelab > inference

I don’t have gobs of money to throw at frontier labs, and I also don’t trust any of them. I try to keep anything sensitive or novel that I don’t want to share with the world locally. Inference runs on my MacBook Pro M1 with 32GB of unified memory, fronted by llama.cpp and MLX. Local sweet spots: gemma3-12b for general reasoning, qwen3-14b for code, parakeet-mlx for ASR.

LiteLLM sits in front of every backend as a single OpenAI-compatible gateway. Agents in the cluster reach it over Tailscale and ask for a model by name; LiteLLM decides whether the request stays on the M1 or fans out to a cloud provider. That decoupling means I can swap a backend, retune routing, or pin a per-model budget without touching any agent code. When a request needs a frontier model the M1 cannot hold (large context, top-tier capability, heavy tool-use loops), LiteLLM falls back to NVIDIA NIM or OpenCode Go. OpenCode Go’s subscription limits and per-request timeouts cap exposure on both.

flowchart LR Agents["Homelab Agents
Talos + vclusters"]:::agent subgraph M1["MacBook Pro M1 / 32GB"] direction TB LiteLLM["LiteLLM Router"]:::edge Backends["llama.cpp · MLX"]:::platform Models["gemma3-12b · qwen3-14b · parakeet-mlx"]:::app LiteLLM --> Backends Backends --> Models end subgraph Cloud["Cloud Fallback"] direction TB NIM["NVIDIA NIM"]:::external OC["OpenCode Go"]:::external end Agents -.->|Tailscale| LiteLLM LiteLLM -->|frontier models| Cloud classDef external stroke:#64748b classDef edge stroke:#fbbf24 classDef platform stroke:#4ade80 classDef app stroke:#60a5fa classDef agent stroke:#a78bfa

~/homelab > status

Forgejo 88.82%
Git server
Vikunja 88.82%
Task tracker
Postiz 88.82%
Social scheduler
Windmill 88.82%
Workflow engine
MinIO 88.82%
Object store
Grafana 88.83%
Metrics frontend
Talos API 100.00%
Cluster control plane
Debian Gateway 100.00%
NAT + subnet router

Probed Jul 4 12:20 UTC by an in-cluster CronJob. This is a static build, so the grid is point-in-time: it reflects whatever snapshot was current the last time the site was rebuilt.

Uptime is averaged over a 30-day rolling window.

~/homelab > metrics

Cluster CPU (24h)
26.0% min 25.6 · max 26.5
Node memory used
talos-cp-01 14%
talos-worker-02 46%
talos-worker-01 47%
Pods running
90

Snapshot Jul 4 12:20 UTC from in-cluster Prometheus. Static build, so this is point-in-time: it reflects whatever snapshot was current the last time the site was rebuilt.

~/homelab > field-notes

changelog

What I Run, and Why

The reasoning behind the choices in my homelab: why Talos, why Debian as the gateway, why local inference, and where convenience and control trade off.

build-log Approximately March 2026 to present, Active

Self-Hosted Agents: What I Tried, What I Pulled, and How Proton Pass Became the Credential Surface

Months of figuring out what to give a self-hosted agent access to. Installed Openclaw, lost sleep, uninstalled it the next day, then spent a couple of months working out how Proton Pass could be the credential layer. Today: two long-running Hermes Agents. Hidalgo on a ThinkPad with its own ESP32 workbench, and Hiero in its own vcluster on the Talos cluster.

build-log December 2024 to present, Active

The Debian Foundation: NAT Gateway, NVR, and What Doesn't Belong in Kubernetes

A single bare-metal Debian server running NAT, Tailscale subnet routing, a five-camera Frigate NVR with a Coral USB TPU, HomeAssistant, Pinchflat, and Grafana dashboards. Hardware bill comes in under $600 not counting the outdoor cabling.

build-log March 2026 to April 2026, Complete

From GitLab to Forgejo: Self-Hosted Git on Kubernetes

Moved self-hosted source control from Dockerized GitLab CE on the Debian server to Forgejo on the Talos Kubernetes cluster, behind Cilium Gateway API with cert-manager and ExternalDNS. Full repo migration, about a month of parallel running, then GitLab CE was decommissioned.

build-log February 2025 to present, Active

Bare-Metal Talos Kubernetes on Three Lenovo M700s

A three-node Talos Linux Kubernetes cluster on $330 of refurbished Lenovo M700 mini-PCs. The migration from k3s to Talos, the platform layer (Cilium Gateway API, Longhorn, cert-manager, ExternalDNS, kube-prometheus-stack), and the network design that makes the cluster sit behind a single Debian gateway.

postmortem 2026-06-19 to 2026-06-20

Debian LUKS Locked Me Out of My Cluster for 14 Hours

An overnight reboot on the LUKS-encrypted Debian server that fronts my Talos cluster halted at the passphrase prompt. The cluster and every hosted service were unreachable until I drove to the box and typed it in.