RAFAEL REIS case study
llmops · self-hosted

12 years of homelab: my self-hosted AI stack

TL;DR

Stack: Proxmox · ZFS · RTX 3090 (GPU passthrough) · llama.cpp · Qwen · pgvector · Tailscale · Cloudflare Tunnel · Grafana · Borg

The lab that became a method

Calling this a "homelab" is technically accurate the way calling a Boeing a "vehicle" is. The foundation: a Proxmox hypervisor on a dual-Xeon server (48 threads, 160 GB of ECC RAM), ZFS storage, two GPUs on passthrough — one for media, one dedicated to LLM inference — and 40+ services across 3 environments: the local host, an ARM VPS in the cloud, and LXC containers. Everything deployed from git, with the same GitOps pattern that runs my company's SaaS — this is where the pattern was proven first.

A number I find more telling than the 40+ active services: the directory of decommissioned stacks holds twice that. SSO providers, four generations of monitoring stack, orchestrators — tested seriously, compared, and retired with notes. A good homelab isn't the one that hoards services: it's the one that evaluates and kills them.

And it's real, daily use: syncs every 15 minutes, nightly synthesis jobs, an automated morning briefing on weekdays, daily backups. The lab isn't a showcase — it's infrastructure I depend on.

Networking: the answer to "which port did you open?" is none

No port-forwarding on the router. Public exposure goes out through Cloudflare Tunnel, with routing declared by labels on the containers themselves — bringing a service up already publishes it, with an access policy (SSO) in front of the admin panels. Internal traffic rides a Tailscale mesh (zero-trust, per-node identity). At the physical edge, an OPNsense firewall with dual WAN and automatic failover.

The practical effect: from the outside, the attack surface is that of someone with nothing running at home — and on the inside, every access has an identity.

the network, from the inside out
internet Cloudflare Tunnel SSO in front of admin panels HOMELAB 40+ services published by labels on the container itself the connection dials out my devices Tailscale mesh · per-node identity 0 open ports on the router
the external surface is that of someone with nothing at home · the arrow points outward on purpose

The AI stack

Inference: 24 GB of VRAM is the currency

A dedicated VM gets the RTX 3090 via GPU passthrough and runs llama.cpp with a model orchestrator in front — a "VRAM scheduler" that loads and unloads models on demand, with eviction cost rules based on measured real-world usage (in my logs, embedding requests outnumber chat by more than 20:1 — so the embeddings model is never evicted; it stays hot 24/7).

The daily catalog: an embeddings model and a reranker resident, plus a quantized ~35B-parameter Qwen MoE with 128K context and a quantized KV cache — all of it coexisting in ~20 of the 24 GB. For heavy work, larger models with 256K context (one of them with vision) take the whole card, at 23.7 of 24 GB. This is LLMOps in practice: quantization as a conscious trade-off, a VRAM budget as capacity planning, and orchestration as scheduling — the same infrastructure disciplines, in a new currency.

the VRAM scheduler, on replay
VRAM: 20.4 / 24 GB
qwen 35B · 128K context 27B with vision · 23.7 GB · whole card 0 12 GB 24 GB
boot · bge-m3 (embeddings) resident 24/7 · 1.2 GB
load reranker · 0.9 GB
chat · load quantized qwen 35B 128K · 18 GB
vision job · evict the daily set · load 27B · 23.7 GB
job done · restore the daily set · 20.4 / 24 GB
the scheduler's real sequence · eviction guided by measured usage: embeddings outnumber chat 20:1, so they're never evicted

Optimization with a number attached: moving the embeddings pipeline from CPU to GPU dropped the processing of a large document from ~38 s to ~4.6 s.

RAG and the personal knowledge graph

On top of the inference layer, a personal knowledge graph: 2,500+ pages, thousands of links and timeline entries, with hybrid search — vectors (pgvector) + Portuguese full-text — and 100% of the corpus embedded locally. Every night, a synthesis job walks the graph and writes reflections connecting what came in that day.

That nightly job is the best cost story in this case study: it first ran on cloud APIs — and hit rate limits, costing cents per run, every day. I moved the whole synthesis to the local GPU: from cost per call to the marginal cost of electricity, no rate limits, with data that never leaves the house. Cloud for the frontier, local for recurring volume — knowing which is which is the job.

Agents: the homelab audits the company

The top floor of the stack is agent runtimes: an agent gateway with tools and memory, and — my favorite — an autonomous pentest agent (open-source, orchestrated by a workflow engine) that runs against the code of my own SaaS, using the local LLM as its brain. The lab at home doing continuous red-teaming of the company, at the cost of electricity. One of its findings became a postmortem and a patch sent upstream.

Run like production (otherwise it's just a pile of containers)

40+active services
3environments
2 yearsof metrics retention
15+postmortems written

Why self-host AI (and what it taught me)

Other case studies

Migrating 50+ live tenants across data centers, with zero downtime Hot replica, atomic cutover, and proof by packet capture, not optimism. A one-person platform team 60+ tenants, two data centers, one engineer. By architecture, not heroics.

Always happy to compare notes with people who run real infrastructure.

LinkedIn