Bumblesloth Homelab — System Reference
Purpose: This file is shared project knowledge for all homelab-related conversations. It describes the full infrastructure so that any discussion (game servers, cloud, networking, etc.) has the context it needs.
Hardware
| Component |
Detail |
| Machine |
HP ProDesk 600 G6 SFF |
| CPU |
Intel i5-10500 (6 cores / 12 threads) |
| RAM |
16 GB DDR4 UDIMM (288-pin desktop, not SO-DIMM) |
| Storage |
512 GB NVMe SSD (single drive) |
| GPU |
Integrated Intel UHD 630 (no dedicated GPU) |
| Expansion |
1× free PCIe x16 slot, 1× 3.5" bay |
| Form factor |
Small Form Factor (SFF) — limits internal expansion |
Operating System
- OS: Ubuntu Server 24.04 LTS (HWE kernel, minimized install, no snaps)
- User:
bumblesloth
- Hostname:
slothhive
- IPv6: Disabled system-wide (
/etc/sysctl.d/99-disable-ipv6.conf) and in Docker (daemon.json)
Network Configuration
Local Network
| Setting |
Value |
| IP |
192.168.178.30 (DHCP with static lease via FRITZ!Box MAC binding) |
| Subnet |
192.168.178.x |
| Netplan config |
/etc/netplan/50-cloud-init.yaml (dhcp4: no — eno1 enslaved to br0) + /etc/netplan/99-bridge.yaml |
| Network bridge |
br0 — bridges eno1 for VM LAN access; inherits eno1 MAC (e8:d8:d1:d3:05:bd) |
Router
| Setting |
Value |
| Current router |
FRITZ!Box 5690 Pro (Deutsche Telekom fiber, SFP module) |
| DHCP DNS |
AdGuard Home pushed via DHCP to all LAN devices (192.168.178.30) |
| DynDNS |
Configured directly on FRITZ!Box — auto-updates ipv64.net on IP change |
Public Access
| Setting |
Value |
| Public IP |
Dynamic (DynDNS managed) |
| DynDNS provider |
ipv64.net |
| Base domain |
bumblesloth.ipv64.net |
| A record |
bumblesloth.ipv64.net → public IP (auto-updated by FRITZ!Box) |
DNS — AdGuard Home
- Wildcard rewrite:
*.bumblesloth.ipv64.net → 192.168.178.30 (covers all subdomains on LAN)
- Desktop DNS (CachyOS): received automatically via FRITZ!Box DHCP — no manual override needed
- All devices on LAN receive AdGuard Home as DNS via FRITZ!Box DHCP
DNS Records (CNAME → bumblesloth.ipv64.net)
| Subdomain |
Service |
cloud.bumblesloth.ipv64.net |
Nextcloud |
photos.bumblesloth.ipv64.net |
Immich |
actual-budget.bumblesloth.ipv64.net |
Actual Budget |
adguard.bumblesloth.ipv64.net |
AdGuard Home |
mealie.bumblesloth.ipv64.net |
Mealie |
git.bumblesloth.ipv64.net |
Gitea |
foundry.bumblesloth.ipv64.net |
Foundry VTT |
matrix.bumblesloth.ipv64.net |
Matrix/Synapse |
jellyfin.bumblesloth.ipv64.net |
Jellyfin |
paperless.bumblesloth.ipv64.net |
Paperless-NGX |
homeassistant.bumblesloth.ipv64.net |
Home Assistant |
Firewall (UFW)
Router Port Forwarding
| Port(s) |
Protocol |
Target |
Service |
| 80 |
TCP |
192.168.178.30 |
NPM (HTTP) |
| 443 |
TCP |
192.168.178.30 |
NPM (HTTPS) |
| 222 |
TCP |
192.168.178.30 |
Gitea SSH |
| 2456–2458 |
UDP |
192.168.178.30 |
Valheim |
| 15636 |
UDP |
192.168.178.30 |
Enshrouded (query) |
| 15637 |
UDP |
192.168.178.30 |
Enshrouded (game) |
Remote Access
- Tailscale mesh VPN installed on slothhive, iPhone, and MacBook; MagicDNS enabled for hostname-based access
- CachyOS desktop:
/etc/hosts entry 192.168.178.30 slothhive for local SSH
KVM Virtualisation
Home Assistant OS runs as a KVM virtual machine on slothhive, alongside Docker services.
Installation
Network Bridge
The VM needs a bridge interface to get its own LAN IP. Configured via /etc/netplan/99-bridge.yaml:
/etc/netplan/50-cloud-init.yaml sets eno1 to dhcp4: false to avoid conflict.
Bridge Kernel Settings
Common virsh Commands
Docker Infrastructure
Installation
- Docker CE from official Docker repository (not distro packages)
- Docker Compose plugin (
docker compose, not docker-compose)
- User
bumblesloth in docker and www-data groups
Networks
| Network |
Type |
Purpose |
proxy-net |
External, user-defined |
Connects NPM to all web-facing services |
nc-internal |
Bridge (Nextcloud stack) |
Isolates Nextcloud DB/Redis |
paperless-internal |
Bridge (Paperless stack) |
Isolates Paperless DB |
matrix-internal |
Bridge (Matrix stack) |
Isolates Synapse DB |
default (per stack) |
Auto-created |
Internal communication within a compose stack |
Security model: Services behind NPM on proxy-net don't need UFW rules or host port exposure — traffic flows 443/80 → NPM → container internally.
Directory Structure
Homelab Management Script
/usr/local/bin/homelab — supports up, down, status
Design: AdGuard Home and NPM are hardcoded as first/last. All other Docker services live in the STACKS array. The Home Assistant VM is managed separately via virsh commands within the same script.
Startup order: adguard-home → nginx-proxy-manager (3s delay) → HA VM → STACKS in order
Shutdown order: STACKS in reverse → HA VM → nginx-proxy-manager → adguard-home
Auto-start on Boot
- Docker services: Systemd service at
/etc/systemd/system/homelab.service — enabled, runs homelab up after Docker is ready on every boot.
- HA VM:
virsh autostart hass — VM starts independently on boot via libvirt.
Services
Nginx Proxy Manager
| Setting |
Value |
| Container |
jc21/nginx-proxy-manager:latest |
| Ports |
80 (HTTP), 443 (HTTPS), 81 (admin, local only) |
| Network |
proxy-net |
| Admin UI |
http://192.168.178.30:81 |
| Features |
SSL via Let's Encrypt, Force SSL, HTTP/2, Block Common Exploits, WebSockets |
All web services are proxied through NPM. Game servers use direct UDP port forwarding, not NPM.
AdGuard Home
| Setting |
Value |
| Role |
LAN DNS resolver, ad/tracker blocking |
| URL |
https://adguard.bumblesloth.ipv64.net |
| Admin UI |
http://192.168.178.30:3000 |
| Port |
53 (bound to host) |
| Location |
~/homelab/adguard-home/ |
| Network |
proxy-net |
| Wildcard rewrite |
*.bumblesloth.ipv64.net → 192.168.178.30 |
Home Assistant
| Setting |
Value |
| Role |
Home automation, smart home integration |
| URL |
https://homeassistant.bumblesloth.ipv64.net |
| Type |
KVM Virtual Machine (not Docker) — runs Home Assistant OS 18.0 |
| VM name |
hass |
| VM IP |
192.168.178.43 (DHCP via FRITZ!Box) |
| VM disk |
/var/lib/libvirt/images/haos.img (64 GB) |
| VM RAM |
4096 MB |
| VM vCPUs |
2 |
| NPM forward |
192.168.178.43:8123 (by IP, not container name) |
| NPM features |
WebSockets enabled, Force SSL |
| Autostart |
virsh autostart hass |
ZBT-2 USB Passthrough:
| Setting |
Value |
| Device |
Nabu Casa ZBT-2 Thread/Matter border router |
| USB ID |
303a:831a |
| Host path |
/dev/serial/by-id/usb-Nabu_Casa_ZBT-2_94A990D07314-if00 → /dev/ttyACM0 |
| Passthrough method |
virsh attach-device hass ~/zbt2.xml --persistent |
| Mode |
Thread border router (OpenThread Border Router add-on in HA OS) |
ZBT-2 passthrough XML (~/zbt2.xml):
Integrations configured:
- LG webOS TV (
192.168.178.22) — added manually
- iPhone Companion app — connected via
https://homeassistant.bumblesloth.ipv64.net
- ZBT-2 Thread border router — OpenThread Border Router add-on
Notes:
- HA OS runs as a KVM VM because the OpenThread Border Router add-on requires HA OS (not available in HA Container)
- VM uses
br0 bridge for LAN access — gets its own IP directly from FRITZ!Box DHCP
- NPM proxies to VM by IP (
192.168.178.43) since the VM is not on proxy-net
trusted_proxies config not needed in HA OS (handled differently than HA Container)
- mDNS auto-discovery doesn't work across Docker bridge — iOS Companion app must use manual URL
Nextcloud
| Setting |
Value |
| Role |
Documents, file sync, collaboration |
| URL |
https://cloud.bumblesloth.ipv64.net |
| Containers |
nextcloud-app (Nextcloud), nextcloud-db (MariaDB), nextcloud-redis (Redis) |
| Networks |
proxy-net, nc-internal |
| NPM forward |
nextcloud-app:80 |
| PHP memory |
1024M (/usr/local/etc/php/conf.d/memory-limit.ini) |
Users: root (admin, no data), kevin
Cron:
Background jobs set to "Cron" in admin settings.
Notes:
- iOS/macOS CalDAV login can trigger brute-force protection; reset with
occ security:bruteforce:reset <ip>
bumblesloth user is in www-data group for host-level access to user-data directory
- Passwords in docker-compose.yml contain
$ special characters — escaped as $$ to prevent Docker Compose variable interpolation warnings
Immich
| Setting |
Value |
| Role |
Photo & video management, AI tagging, face recognition |
| URL |
https://photos.bumblesloth.ipv64.net |
| Containers |
immich_server, immich_machine_learning, immich_redis, immich_postgres |
| Networks |
proxy-net + default (server), default only (others) |
| NPM forward |
immich_server:2283 |
Users: Kevin, mother
Paperless-NGX
| Setting |
Value |
| Role |
Document management, OCR, tagging |
| URL |
https://paperless.bumblesloth.ipv64.net |
| Containers |
paperless, paperless-db (PostgreSQL), paperless-redis, paperless-gotenberg, paperless-tika, paperless-gpt, ollama |
| Networks |
proxy-net, paperless-internal |
| NPM forward |
paperless:8000 |
Backup: PostgreSQL dump + document export → rsync to USB (/mnt/backup-usb, btrfs) nightly at 2 AM
Manual sync to Proton Drive: Fish function paperless-backup on CachyOS desktop (rclone automation pending upstream fix — PRs #9080/#9081)
OCR workflow:
- Automated metadata (title, tags, date) via paperless-gpt +
qwen2.5:7b
- Manual OCR for difficult documents via Open WebUI +
llama3.2-vision:11b; PDFs converted with pdftoppm (poppler)
- Claude (vision) as fallback for manual transcription
Jellyfin
| Setting |
Value |
| Role |
Media streaming |
| URL |
https://jellyfin.bumblesloth.ipv64.net |
| Network |
proxy-net |
| NPM forward |
jellyfin:8096 |
| Hardware accel |
Intel QSV via /dev/dri (Intel UHD 630) |
| Media path |
/srv/media/ (host mount) |
Mealie
| Setting |
Value |
| Role |
Recipe manager |
| URL |
https://mealie.bumblesloth.ipv64.net |
| Network |
proxy-net |
| Backend |
SQLite |
Gitea
| Setting |
Value |
| Role |
Self-hosted Git |
| URL |
https://git.bumblesloth.ipv64.net |
| Network |
proxy-net |
| Backend |
SQLite |
| SSH |
Port 222 (UFW + FRITZ!Box port forwarding configured) |
| NPM forward |
gitea:3000 |
| Features |
WebSockets enabled, Block Common Exploits enabled |
Actual Budget
| Setting |
Value |
| Role |
Personal finance (replaced Firefly III) |
| URL |
https://actual-budget.bumblesloth.ipv64.net |
| Network |
proxy-net |
| NPM forward |
actual-budget:5006 |
Foundry VTT
| Setting |
Value |
| Role |
TTRPG virtual tabletop |
| URL |
https://foundry.bumblesloth.ipv64.net |
| Network |
proxy-net |
| NPM |
WebSockets must be enabled in NPM proxy host |
Matrix / Synapse
| Setting |
Value |
| Role |
Private self-hosted chat |
| URL |
https://matrix.bumblesloth.ipv64.net |
| Server name |
matrix.bumblesloth.ipv64.net (permanent — chosen to avoid .well-known delegation) |
| User IDs |
@kevin:matrix.bumblesloth.ipv64.net |
| Containers |
synapse, matrix-db (PostgreSQL), coturn |
| Networks |
proxy-net, matrix-internal |
| NPM |
WebSockets must be enabled |
| Federation |
Disabled |
| Registration |
Manual only |
Enshrouded (Game Server)
| Setting |
Value |
| Image |
mornedhels/enshrouded-server:latest |
| Container |
enshrouded |
| Ports |
UDP 15636 (query), UDP 15637 (game) |
| Backups |
Every 6 hours, max 4 kept |
| Max players |
8 |
| Update cron |
Mondays at 4 AM |
Force update: docker compose exec enshrouded supervisorctl start enshrouded-force-update
Note: Server version may lag behind client after patches — Keen Games publishes dedicated server updates separately. Use force update command if version mismatch occurs.
Valheim (Game Server)
| Setting |
Value |
| Image |
ghcr.io/lloesche/valheim-server |
| Container |
valheim |
| Ports |
UDP 2456–2458 |
| Mod framework |
BepInEx (disabled — glibc incompatibility; vanilla only) |
| Auto-update |
Only when idle |
| Backups |
Every 6 hours, max 10 kept, max 7 days |
| Public |
false (private server) |
| World |
BumbleWorld (in ./config/worlds_local/) |
World switching: Change WORLD_NAME in docker-compose.yml, run docker compose down && docker compose up -d.
Adding mods: Drop .dll files in ./config/bepinex/plugins/, restart container.
CUPS / AirPrint Bridge
| Setting |
Value |
| Role |
AirPrint bridge for Epson ET-2810 printer |
| Image |
drpsychick/airprint-bridge:latest |
| Container |
cups |
| Location |
~/homelab/cups/ |
Common Commands
Docker
Homelab Script
KVM / Home Assistant VM
Enshrouded
Nextcloud (OCC)
System
Known Issues & Notes
- IPv6 disabled — Docker Hub pulls fail over IPv6. Disabled system-wide in sysctl and in Docker daemon.json.
- BepInEx glibc error — BepInEx doorstop requires GLIBC 2.33+; container has older version. Valheim runs vanilla.
- Nextcloud brute force — iOS/macOS app login can trigger rate limiting. Reset with
occ security:bruteforce:reset <ip>. Use app passwords if 2FA is enabled.
- Hairpin NAT — Accessing public-facing services via domain from inside the LAN may not work on some routers. AdGuard wildcard rewrite handles this on the LAN.
- Single SSD — All data on one 512 GB NVMe. Future plan: NAS for bulk storage, keep databases on SSD.
- Proton Drive rclone automation — Blocked by upstream bug (PRs #9080/#9081 unmerged). Paperless backups synced manually via
paperless-backup Fish function on CachyOS desktop.
- Matrix server name is permanent —
matrix.bumblesloth.ipv64.net used directly; changing it would require a full rebuild.
- WebSockets — Must be explicitly enabled in NPM for Foundry VTT and Matrix/Synapse (and Home Assistant).
- DDR4 UDIMM (288-pin) — HP ProDesk 600 G6 SFF requires standard desktop DIMMs, not SO-DIMM. Important for future RAM upgrades.
- DynDNS auto-update — FRITZ!Box handles updates automatically, but after a power outage verify the public IP matches ipv64.net if services are unreachable externally.
- Nextcloud DB passwords — Contain
$ special characters; escaped as $$ in docker-compose.yml to avoid Docker Compose variable interpolation warnings.
- HA OS requires KVM VM — The OpenThread Border Router add-on (required for ZBT-2 Thread mode) is only available in HA OS, not HA Container. KVM VM coexists with Docker on the same host without conflict.
- HA VM network — VM gets its IP via DHCP from FRITZ!Box through the
br0 bridge. NPM proxies to the VM by IP (192.168.178.43), not container name. If the VM IP changes, update NPM proxy host.
- br0 bridge required for VM — KVM VM LAN access requires
br0 network bridge. UFW must allow routed traffic on br0 (ufw route allow in/out on br0). Bridge netfilter must be disabled (bridge-nf-call-iptables = 0) to avoid Docker iptables conflicts.
- HA VM disk size — VM disk expanded to 64 GB (from default 32 GB) to accommodate HA Core Docker image download during initial setup.
- mDNS limitations — Auto-discovery (for iOS Companion app, LG TV, etc.) does not work across Docker bridge or into the KVM VM from LAN devices. Use manual IP/URL for all HA integrations.
Future Plans