AI & LLM on Your Homelab — A Tutorial
This is a step-by-step guide to running your own AI inference stack at home. Not cloud APIs, not vendor lock-in — your own models, your own hardware, your own data.
Everything stays on your network.
Why Local First
Every prompt you send to a cloud LLM gets logged, analyzed, and potentially used for training. When you’re asking about architecture decisions or reviewing internal code, that’s a real privacy concern, not a theoretical one.
Modern GPUs are capable enough and open models have made local inference practical. The setup isn’t complicated, and the freedom is worth the tradeoffs.
What You’ll Build
A local inference server with a clean web interface, reverse proxy with TLS, and optional Matrix integration for remote access. All services communicate via DNS names — no port numbers to remember.
Core components:
- llama.cpp — inference server with CUDA acceleration
- Open WebUI — web frontend for chatting with models
- Caddy — reverse proxy with automatic TLS via Cloudflare DNS
- Tuwunel — Matrix homeserver (optional, for federation)
- Hermes (Sage) — AI agent with tool access (optional)
Client tools (bonus):
- OpenCode — IDE integration from your laptop
- pi — lightweight agent harness
Prerequisites
- A machine with a CUDA GPU (NVIDIA RTX 3090 or similar)
- Docker + Docker Compose
- Cloudflare account with DNS management
- Domain name(s) for services
- Basic comfort with the terminal
Step 1: llama.cpp Server
The inference engine. Runs quantized GGUF models on your GPU.
Create docker-compose.yaml:
services:
llama-server:
image: ghcr.io/ggml-org/llama.cpp:server-cuda
container_name: llamacpp
restart: unless-stopped
networks:
- cadnet
volumes:
- /path/to/ai-models:/models
deploy:
resources:
limits:
memory: 8G
reservations:
devices:
- driver: nvidia
count: all
capabilities:
- gpu
command: >
-m /models/YOUR_MODEL.gguf -c 8192 --host 0.0.0.0 --port 8080
--n-gpu-layers 99 --flash-attn on --cache-type-k q8_0 --cache-type-v q8_0
-b 256 -ub 256 --no-mmap -np 1
environment:
- MODEL_FILE=YOUR_MODEL.gguf
networks:
cadnet:
external: true
Notes:
- Replace
YOUR_MODEL.ggufwith your actual model filename - Context size (
-c) depends on your GPU VRAM — 8192 is safe for most 3090-class cards - Q4_K_M quantization gives good quality/speed tradeoff
- Model files go in whatever directory you set as
/path/to/ai-models
Getting models: Download GGUF models from HuggingFace. Look for quantized versions from trusted sources like TheBloke or Bartowski. For a starting point, models in the 7B-13B range work well on consumer hardware.
Step 2: Open WebUI
A clean web interface that connects to your llama.cpp server. No Ollama needed when pointing to a remote API.
Create docker-compose.yaml:
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
networks:
- cadnet
volumes:
- ./open-webui:/app/backend/data
environment:
- 'OLLAMA_BASE_URL=http://llamacpp:8080'
- 'WEBUI_SECRET_KEY='
restart: unless-stopped
networks:
cadnet:
external: true
Key configuration:
OLLAMA_BASE_URLpoints to your llama.cpp container on the Docker network- Open WebUI thinks it’s talking to Ollama, but llama.cpp serves a compatible API
- Data persists in the
./open-webuivolume
Step 3: Caddy Reverse Proxy
Caddy handles TLS automatically via Cloudflare DNS. One config, all services covered.
Docker Compose
services:
caddy:
image: caddy:latest
restart: unless-stopped
environment:
- CF_TOKEN=${CF_TOKEN}
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./conf:/etc/caddy
- ./site:/srv
- ./caddy_data:/data
- ./caddy_config:/config
networks:
- cadnet
networks:
cadnet:
external: true
Set CF_TOKEN in your environment — this is a Cloudflare API token with DNS zone edit permissions.
Caddyfile
{
acme_dns cloudflare {env.CF_TOKEN}
}
(llamacpp) {
import cfdns
encode gzip
header Strict-Transport-Security max-age=63072000
reverse_proxy llamacpp:8080
}
(openwebui) {
import cfdns
encode gzip
header Strict-Transport-Security max-age=63072000
reverse_proxy open-webui:8080
}
llm.yourdomain.com {
import llamacpp
}
chat.yourdomain.com {
import openwebui
}
Replace yourdomain.com with your actual domain. Pick whatever subdomain names make sense for you.
How it works:
- Caddy automatically gets TLS certificates via Cloudflare DNS challenge
- No manual cert management
- Each service gets its own DNS name
- Docker network name (
llamacpp,open-webui) resolves automatically
Step 4: Optional — Matrix Federation
If you want to chat with your models over Matrix (federated, encrypted, your own server), Tuwunel is a lightweight Rust-based homeserver.
Create docker-compose.yaml:
services:
homeserver:
image: jevolk/tuwunel:latest
restart: unless-stopped
container_name: twunnel
networks:
- cadnet
volumes:
- ./db:/var/lib/tuwunel
- ./tuwunel.toml:/etc/tuwunel.toml
environment:
TUWUNEL_SERVER_NAME: your-matrix-domain.com
TUWUNEL_DATABASE_PATH: /var/lib/tuwunel
TUWUNEL_PORT: 6167
TUWUNEL_ALLOW_REGISTRATION: 'false'
TUWUNEL_ALLOW_FEDERATION: 'true'
TUWUNEL_TRUSTED_SERVERS: '[\"matrix.org\"]'
TUWUNEL_ADDRESS: 0.0.0.0
networks:
cadnet:
external: true
Add to your Caddyfile:
your-matrix-domain.com {
import cfdns
encode gzip
header Strict-Transport-Security max-age=63072000
reverse_proxy twunnel:6167
}
Why Tuwunel? It’s not Synapse with all its memory overhead. Written in Rust, uses SQLite, no separate database server. Federation works with the wider Matrix network.
Step 5: Optional — Hermes Agent
The Sage agent runs on your homelab and connects through Matrix. It has tools — terminal access, file reading, web search — so it’s not just a text completion engine.
Create docker-compose.yaml:
networks:
cadnet:
external: true
services:
hermes:
image: nousresearch/hermes-agent:latest
container_name: sage
restart: unless-stopped
command: gateway run
networks:
- cadnet
volumes:
- ./hermes-data:/opt/data
- /path/to/host-docker:/opt/data/host-docker:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- HERMES_DASHBOARD=1
deploy:
resources:
limits:
memory: 2G
cpus: "2.0"
Add to Caddyfile:
sage.yourdomain.com {
import cfdns
encode gzip
header Strict-Transport-Security max-age=63072000
reverse_proxy sage:9119
}
What it does: When you’re out, you talk to it through Matrix. It can check server status, run commands, or look up information. Not a chat bot — an agent that can actually do things.
Architecture Overview
[Your Domain]
├── llm.yourdomain.com → Caddy → llama.cpp (GPU box)
├── chat.yourdomain.com → Caddy → Open WebUI (main box)
├── your-matrix-domain.com → Caddy → Tuwunel (main box)
└── sage.yourdomain.com → Caddy → Hermes agent (main box)
All on Docker network 'cadnet'
All TLS via Cloudflare DNS
All DNS names, no port numbers
Client Tools (Bonus)
OpenCode
Run on your laptop, point to your local inference server. It reads code, suggests refactors, and helps navigate unfamiliar codebases. Everything stays on your network.
# Point OpenCode to your inference server
export OPENCODE_API_BASE=https://llm.yourdomain.com
pi
Similar concept — lightweight agent harness that runs on your laptop and connects to your homelab inference.
What Works Well
- Code review and refactoring suggestions
- Drafting technical documents and emails
- Explaining unfamiliar codebases
- Brainstorming architecture approaches
- Having a working tool, not a chat toy
What Doesn’t
- Anything requiring factual accuracy without verification
- Long-context tasks beyond the model’s window
- Anything you’d trust without reading the output first
What I’d Change
Model selection matters more than I expected. Start with a smaller model and work up — you’ll learn what your GPU can handle before committing to larger ones. Context size is a real constraint; 8K is fine for most tasks, but if you need more, you’ll need more VRAM.
The local-first constraint means you can experiment freely. If a model hallucinates or gives bad advice, it doesn’t leak to anyone. That freedom is the whole point.
Next Steps
Once this is running, you have a few options:
- Add more models and swap between them
- Connect additional frontends (mobile apps, desktop clients)
- Set up the Hermes agent for automated tasks
- Join the Matrix federation and chat with other homeservers
Pick what fits your use case. The stack is yours — change it however you want.