Homelab Reference¶
Hardware¶
| Device | Role | Specs |
|---|---|---|
| Lenovo ThinkCentre M710q | Proxmox host / home server | 8GB RAM (dual-channel upgrade planned), Intel HD 630 iGPU |
| HP EliteBook X G1a | Daily driver laptop / LLM inference | AMD Ryzen AI 9 HX PRO 375, Radeon 890M iGPU, 32GB RAM, Windows 11 Pro |
| Raspberry Pi 4 | Secondary Pi-hole | Pi OS Lite, headless, always-on |
RAM upgrade notes: Match existing DIMM speed when adding a second stick — mismatched speeds downclock to the slower module. Equal-size sticks (e.g. 8+8) outperform mismatched (e.g. 16+8), which drops into a hybrid single/dual channel mode that loses much of the bandwidth benefit.
Network¶
| Service | IP | Notes |
|---|---|---|
| Proxmox host | 10.0.0.X |
Web UI at :8006 |
| Ubuntu VM | 10.0.0.Y |
Docker host |
| Pi-hole LXC (primary) | 10.0.0.Z |
DNS, web UI at /admin |
| Pi-hole Pi 4 (secondary) | 10.0.0.W |
Secondary DNS, web UI at /admin |
| InfluxDB LXC | 10.0.0.V |
Time-series DB for HA data |
| Jellyfin LXC | DHCP reserved | Web UI at :8096 |
| Home Assistant VM | DHCP reserved | |
| Chromecast | 10.0.0.U |
DHCP reservation; DNAT rule redirects port 53 to Pi-hole |
DHCP reservations are managed in UniFi.
DNS is handled by Pi-hole (primary and secondary), with Unbound as the upstream recursive resolver at 127.0.0.1:5335. Fallback DNS in UniFi is set to a public resolver.
Local DNS records must be added to both Pi-hole instances. Use a .localdomain (or similar non-reserved) suffix — never .local, which conflicts with mDNS used by Home Assistant and Apple devices.
Windows requires a dot in DNS names, so single-label hostnames will fail to resolve. Always use the fully-qualified
name.localdomainform from Windows clients.
Chromecast DNS hijack: Chromecasts hard-code public DNS and ignore DHCP-supplied DNS servers. A Destination NAT rule in UniFi Policy Engine redirects port 53 traffic from the Chromecast to the local Pi-hole. UniFi client groups cannot be used as a NAT rule source — must use per-IP rules.
Proxmox Host¶
Access: https://<proxmox-host>:8006
Login gotcha: the realm dropdown defaults change between versions. Make sure it's set to Linux PAM standard authentication rather than "Proxmox VE authentication server" — easy to accidentally leave on the wrong realm and get auth errors.
VMs and containers (all set to Start at Boot):
| Name | Type | Role |
|---|---|---|
| Home Assistant OS | VM | Smart home |
| Ubuntu Server | VM | Docker host |
| Pi-hole + Unbound | LXC | DNS / ad blocking |
| InfluxDB | LXC | Time-series database |
| Jellyfin | LXC | Media server |
LXC "Start at Boot" is not enabled by default. Set it manually: select the container → Options → Start at Boot → Yes. Easy to miss until your first reboot leaves DNS down.
External HDDs¶
Two NTFS drives physically attached to the Proxmox host, mounted at boot via /etc/fstab:
UUID=<uuid-1> /mnt/media/<drive-1> ntfs defaults,nofail,rw,uid=0,gid=0,umask=000 0 0
UUID=<uuid-2> /mnt/media/<drive-2> ntfs defaults,nofail,rw,uid=0,gid=0,umask=000 0 0
umask=000is required for write access through the NFS → Samba → Windows chain. NTFS permissions are controlled byumask, notchmod/chown— those have no effect on NTFS mounts.nofailensures the host boots normally even if a drive is unplugged.
The drives are NFS-exported to the Ubuntu VM. Ubuntu's /etc/fstab:
<proxmox-host>:/mnt/media/<drive-1> /mnt/media/<drive-1> nfs defaults,nofail 0 0
<proxmox-host>:/mnt/media/<drive-2> /mnt/media/<drive-2> nfs defaults,nofail 0 0
The drives are also bind-mounted into the Jellyfin LXC via /etc/pve/lxc/<CTID>.conf:
mp0: /mnt/media/<drive-1>,mp=/mnt/media/<drive-1>
mp1: /mnt/media/<drive-2>,mp=/mnt/media/<drive-2>
Adding a new drive (full process)¶
# 1. Proxmox host — identify and mount
lsblk
blkid /dev/sdX
mkdir -p /mnt/media/newdrive
echo "UUID=XXXX /mnt/media/newdrive ntfs defaults,nofail,rw,uid=0,gid=0,umask=000 0 0" >> /etc/fstab
mount /mnt/media/newdrive
ntfsfix /dev/sdX # if it mounts read-only
# 2. Proxmox host — add NFS export
nano /etc/exports
# Add: /mnt/media/newdrive <ubuntu-vm-ip>(rw,sync,no_subtree_check,no_root_squash)
exportfs -ra
# 3. Ubuntu VM — mount via NFS
sudo mkdir -p /mnt/media/newdrive
sudo mount <proxmox-host>:/mnt/media/newdrive /mnt/media/newdrive
echo "<proxmox-host>:/mnt/media/newdrive /mnt/media/newdrive nfs defaults,nofail 0 0" | sudo tee -a /etc/fstab
# 4. Ubuntu VM — add Samba share
sudo nano /etc/samba/smb.conf
sudo systemctl restart smbd
# 5. Jellyfin LXC (if needed)
nano /etc/pve/lxc/<CTID>.conf
# Add: mp2: /mnt/media/newdrive,mp=/mnt/media/newdrive
pct stop <CTID> && pct start <CTID>
Key file locations (Proxmox host)¶
| Thing | Path |
|---|---|
| Persistent mounts | /etc/fstab |
| LXC configs | /etc/pve/lxc/<CTID>.conf |
| NFS exports | /etc/exports |
Ubuntu VM¶
tmux auto-attach in ~/.bashrc makes SSH sessions resilient to dropped connections (sessions don't survive reboots, but do survive network blips):
if [ -z "$TMUX" ]; then
tmux attach 2>/dev/null || tmux new
fi
Docker (docker.io, daemon-based, not Podman) runs all containerized services.
Samba (Windows file sharing)¶
Shares the media drives to the Windows network. Config at /etc/samba/smb.conf. Key share definitions:
[Share1]
path = /mnt/media/<drive-1>
browseable = yes
read only = no
guest ok = yes
[Share2]
path = /mnt/media/<drive-2>
browseable = yes
read only = no
guest ok = yes
Write access works because
umask=000is set on the NTFS mounts at the Proxmox level. Samba itself has noforce userset — connects as guest.
Docker containers¶
| Container | Port | Notes |
|---|---|---|
| Grafana | :3000 |
Metrics dashboard; data persisted to a host volume |
| Node-RED | — | Automation flows; needs HA Long-Lived Access Token |
| Open WebUI | :3000 |
Points at local LLM inference endpoint |
Node-RED ↔ Home Assistant: Requires the node-red-contrib-home-assistant-websocket package, the correct Base URL (http://<ha-ip>:8123), and a Long-Lived Access Token generated from the HA profile page.
Grafana ↔ InfluxDB networking: when both are Docker containers on the same host, they need to share a Docker network for hostname resolution. docker network connect <influxdb-network> grafana connects them; the InfluxDB container is then reachable as http://influxdb:8086 from Grafana. When InfluxDB lives outside Docker (e.g. as an LXC), use its IP directly as the data source URL.
Cloudflare Tunnel & Remote Access¶
cloudflared runs on the Ubuntu VM, creating an outbound tunnel to Cloudflare. No router ports need to be opened.
Cloudflare Access should be configured in front of any admin-facing subdomains (Proxmox, Pi-hole, NPM) — e.g. a Google login policy tied to a single email.
Nginx Proxy Manager (NPM): Docker container on the Ubuntu VM for internal reverse proxy routing. Handles clean internal HTTPS URLs for services that don't need to be publicly exposed.
Tunnel + video streaming: Cloudflare's terms restrict serving video through the CDN proxy. The tunnel itself is fine; the restriction applies to the orange-cloud proxy layer. Set the relevant DNS record to "DNS only" (grey cloud) for any video-streaming subdomain.
Bypass Cloudflare on LAN: add a local DNS override on Pi-hole pointing the public hostname at the internal service IP. Local clients then bypass Cloudflare entirely — protects against Cloudflare outages for local access.
Home Assistant VM¶
Add-ons:
- Terminal & SSH (official add-on) — use this for shell access, not third-party alternatives.
HACS: Installed via curl -fsSL https://get.hacs.xyz | bash - from the Terminal & SSH add-on shell.
Active integrations:
- Proxmox VE (built-in) — CPU, memory, disk, node status; uses an API token with
PVEAuditorrole - GE Appliances / SmartHQ (HACS:
simbaja/ha_gehome) — appliance control and temperature monitoring - Jellyfin — media server integration
- InfluxDB — configured via UI (Settings → Devices & Services)
- UniFi — network device monitoring
HassOS shell: common Linux tools like
wgetmay be absent. Usecurlinstead. Always use the official Terminal & SSH add-on rather than community alternatives.
Jellyfin LXC¶
Hardware transcoding: Intel Quick Sync via VA-API (/dev/dri/renderD128). H.264 and H.265 enabled.
Clients: Chromecast with Google TV (Android TV Jellyfin app), browser. For Windows desktop, use Jellyfin Media Player (MPV-based) rather than the DLNA "Connect to Media Server" option in Windows.
File naming: must match the expected format for library matching, e.g. Movie Title (2001).mkv. Scan libraries manually after adding files: Dashboard → Libraries → Scan All Libraries.
Plugins worth installing:
| Plugin | Notes |
|---|---|
| Intro Skipper | Detects/skips TV intros; add repo manually; initial scan is CPU-heavy — run overnight |
| Open Subtitles | Official plugin; needs a free account |
| Jellyscrub | Preview thumbnails on scrubbing |
| Playback Reporting | Play stats and history graphs |
| Home Screen Sections | Netflix-style home screen sections |
Key paths inside container:
| Thing | Path |
|---|---|
| Config | /etc/jellyfin/ |
| Logs | /var/log/jellyfin/ |
Right-size LXC RAM allocations to your host's total. Jellyfin's default LXC template requests more than is necessary for direct play — tune down for constrained hosts.
Pi-hole + Unbound¶
Pi-hole handles DNS sinkhole / ad blocking. Unbound runs as a local recursive resolver at 127.0.0.1:5335 — Pi-hole points upstream at Unbound rather than a public DNS provider, which avoids leaking DNS queries to a single third party.
A secondary Pi-hole on a Raspberry Pi 4 provides DNS resilience — configured as the second DNS server in UniFi DHCP settings. If the primary goes down (Proxmox host reboot, LXC issue), DNS keeps working.
Local DNS records must be added to both Pi-hole instances. They don't sync automatically. There are sync tools (
gravity-sync, etc.) but adding records manually to two instances is fine for a small set.
Gotchas & Lessons Learned¶
- NTFS + NFS + Samba write permissions: controlled by
umask=in the fstab mount options —chmod/chowndon't affect NTFS mounts. Useumask=000for full write access through the whole chain. - Unmounting busy drives: stop Samba (
sudo systemctl stop smbd) on Ubuntu, stop Jellyfin (pct stop <CTID>) on Proxmox, stop the NFS server if needed — in that order — before unmounting. Or just reboot Proxmox. ntfsfix: if an NTFS drive mounts read-only, runntfsfix /dev/sdXon the host before remounting. Usually clears a "dirty" flag left by an unclean Windows shutdown.- Windows DNS: single-label hostnames (no dot) fail on Windows. Always use
name.localdomainformat in Pi-hole local DNS records. Never.local. - Proxmox login realm: must select "Linux PAM standard authentication" at login — easy to leave on the wrong realm and get auth errors.
- HassOS shell:
wgetmay be absent — usecurl. Always use the official Terminal & SSH add-on. - LXC Start at Boot: off by default — set it manually per container.
- LXC data persistence: LXC containers persist all data across restarts — they behave like lightweight VMs. Only Docker containers without volumes lose data on removal.
- Cloudflare Tunnel ToS: video streaming via tunnel is fine as long as the DNS record is grey-clouded (DNS only). The video restriction applies to the CDN proxy layer, not the tunnel itself.