Homelab media + local AI stack

Docker Compose stack combining VPN-locked torrents (*arr + qBittorrent via gluetun), Usenet (SABnzbd), media (Jellyfin), local LLM inference (Prism llama-server + Open WebUI), Forgejo git, OpenHands agent, Caddy LAN landing page, and Cloudflare DDNS.

spec v1.2.0 generated 2026-07-23T02:54:38Z host 192.168.8.123 stack /opt/stack

Primary host

Hardware profiles

Apply with ./scripts/apply-profile.sh mini|mid|gaming

mini

Beelink / SER5-class mini PC

  • Inference: cpu (ngl=0)
  • Context: 32768
  • Model: Ternary-Bonsai-27B-Q2_0.gguf
  • CPU preferred on APU; do not force NGL in docker-compose.override.yml

mid

~8GB AMD discrete GPU, ~16GB system RAM

  • Inference: vulkan (ngl=99)
  • Context: 8192
  • Model: Qwen3.5-9B-abliterated-v2-MAX.Q4_K_M.gguf
  • Fits VRAM; use KV4 for memory

gaming

~16GB AMD GPU, ~64GB system RAM

  • Inference: vulkan (ngl=99)
  • Context: 32768
  • Model: Ternary-Bonsai-27B-Q2_0.gguf
  • Full Vulkan + large context

Services

All services deploy by default (docker compose up -d). Replace HOST with 192.168.8.123. Links use plain http:// (no TLS).

IDNameCategoryPortURLPurpose
landingLanding (Caddy)gateway80http://192.168.8.123/Dark-mode LAN resource map
gluetungluetunvpnWireGuard VPN + killswitch
qbittorrentqBittorrentdownload8080http://192.168.8.123:8080/Torrent client (VPN-only via gluetun)
sabnzbdSABnzbddownload8085http://192.168.8.123:8085/Usenet NZB downloader
prowlarrProwlarrdownload9696http://192.168.8.123:9696/Indexer manager (torrents + NZB)
flaresolverrFlareSolverrdownload8191http://flaresolverr:8191 (internal)Cloudflare JS challenge proxy for Prowlarr
sonarrSonarrmedia8989http://192.168.8.123:8989/TV automation
radarrRadarrmedia7878http://192.168.8.123:7878/Movie automation
lidarrLidarrmedia8686http://192.168.8.123:8686/Music automation
bazarrBazarrmedia6767http://192.168.8.123:6767/Subtitles
jellyfinJellyfinmedia8096http://192.168.8.123:8096/Media server / playback
bonsaiBonsai (llama-server)ai8081http://192.168.8.123:8081/v1Local LLM OpenAI-compatible API (Prism build)
open-webuiOpen WebUIai3000http://192.168.8.123:3000/Chat UI + RAG + tools
searxngSearXNGai8888http://127.0.0.1:8888/ (localhost only)Private search for Open WebUI RAG
openhandsOpenHandsai3001http://192.168.8.123:3001/AI coding agent (always deployed)
forgejoForgejocode3002http://192.168.8.123:3002/Self-hosted Git
cloudflare-ddnsCloudflare DDNSdnsUpdate public IPv4 A records for domain + subdomains

Scripts

After changing secrets

Edit secrets/.env first, then:

cloudflare

  1. ./scripts/render-cloudflare-ddns-env.sh
  2. docker compose up -d cloudflare-ddns --force-recreate

usenet_servers

  1. ./scripts/configure-usenet.sh
  2. Test servers in SABnzbd UI

nzbgeek

  1. Update API key in Prowlarr UI

wireguard

  1. Edit wireguard/wg0.conf
  2. docker compose up -d gluetun --force-recreate
  3. or ./scripts/gluetun-endpoint-watch.sh

stack_env

  1. Edit .env
  2. docker compose up -d (+ build bonsai if backend changes)

Fresh install

  1. cp .env.example .env; fill STACK_HOST, LAN_SUBNET, usenet, cloudflare keys
  2. Place wireguard/wg0.conf (Endpoint IP + # EndpointHost = hostname)
  3. ./scripts/apply-profile.sh mini|mid|gaming
  4. ./scripts/bootstrap-stack.sh
  5. One-time UI: Prowlarr apps, Jellyfin libraries, Forgejo/Open WebUI admin (demo user/pass)

Day-to-day on server

  1. cd /opt/stack && git pull
  2. docker compose up -d
  3. rebuild bonsai if Dockerfile/profile backend changed

Ansible

Never commit