Skip to content

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.localdomain form 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=000 is required for write access through the NFS → Samba → Windows chain. NTFS permissions are controlled by umask, not chmod/chown — those have no effect on NTFS mounts. nofail ensures 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=000 is set on the NTFS mounts at the Proxmox level. Samba itself has no force user set — 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 PVEAuditor role
  • 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 wget may be absent. Use curl instead. 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/chown don't affect NTFS mounts. Use umask=000 for 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, run ntfsfix /dev/sdX on 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.localdomain format 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: wget may be absent — use curl. 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.