Stood up a smart-home stack: Home Assistant + Music Assistant, host-networked behind NPM
Why: Damien wanted to start running the house — lights, eventually Zigbee/Z-Wave gear — off the server instead of vendor clouds, and to fold local music control into the same place. The HubZ radio stick and an IKEA panel were on hand, so it was time to lay the foundation.
Deployed a single Portainer stack named home-assistant holding two containers: Home Assistant (ghcr.io/home-assistant/home-assistant:stable) on :8123 and Music Assistant (ghcr.io/music-assistant/server:latest) on :8095. Both are reachable externally as ha.skyhouse.dev and ma.skyhouse.dev through NPM. The session ended with the HubZ radio passed through to HA and a first physical light onboarded.
1. Both containers run network_mode: host
Smart-home auto-discovery leans on multicast/broadcast (mDNS, UPnP/SSDP) that does not cross Docker's bridge NAT cleanly. So both containers bind straight to the host's physical NIC with network_mode: host. The trade-off: they have no Docker DNS hostname and share the host's IP/port space — nothing else can already be on 8123 or 8095, and the proxy can't reach them by container name.
- Time alignment: passed
/etc/localtimein read-only and setTZ=America/Los_Angelesso HA's automation schedules and MA's logs match the host clock.
2. Reverse proxy points at the host LAN IP, not a container name
Because the containers left Docker's managed network, the NPM proxy hosts for ha. and ma. target the host's physical LAN IP 192.168.1.136 directly (:8123 and :8095) rather than a Docker service name. WebSockets must be enabled on both — HA's live UI and MA's audio streaming/login handshake both ride a WS upgrade.
I also added ha.skyhouse.dev and ma.skyhouse.dev to the /etc/hosts split-horizon line (→ 192.168.1.136), per the standing rule that every NPM subdomain gets a local-resolution entry so the box resolves its own services on the LAN regardless of public-IP churn.
3. Music Assistant needed a hardened NPM location block
MA is fussy about the auth handshake: NPM's default location handling sometimes drops the X-Forwarded-Host header or sends a generic Connection header that kills the WebSocket upgrade, so the login never completes. Fixed by pasting an explicit location block into the Advanced tab of the ma.skyhouse.dev proxy host:
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_pass http://192.168.1.136:8095;
}
4. Taught Home Assistant to trust the proxy, and handed config back to the UI
HA rejected proxied traffic with 400 Bad Request until it was told the proxy is trusted. Edited the root-owned /opt/appdata/homeassistant/config/configuration.yaml (via sudo) to enable use_x_forwarded_for and declare trusted subnets:
http:
use_x_forwarded_for: true
trusted_proxies:
- 127.0.0.1
- ::1
- 172.16.0.0/12 # all Docker networks (NPM lives here; bridges run 172.17–172.27)
- 192.168.1.0/24 # local LAN
The /12 is deliberately broad: with HA on host networking, proxied requests arrive sourced from whichever Docker subnet NPM sits on, and this box has bridges spanning 172.17 through 172.27 — one CIDR covers them all.
While in there I also deleted the hardcoded homeassistant: block (timezone, internal/external URLs). With those keys present in YAML, HA locks the matching General and Network panels in the web UI. Removing them shifts that config to HA's internal database and re-enables the UI controls.
5. HubZ radio passthrough + first light onboarded
The Silicon Labs HubZ is a combo Zigbee+Z-Wave stick that exposes two serial interfaces. Both are mapped into the HA container by their stable /dev/serial/by-id paths (never raw ttyUSB*, which renumber on replug):
usb-Silicon_Labs_HubZ_Smart_Home_Controller_C1300335-if00-port0→/dev/ttyUSB0(Z-Wave)usb-Silicon_Labs_HubZ_Smart_Home_Controller_C1300335-if01-port0→/dev/ttyUSB1(Zigbee)
First device: a new IKEA Zigbee LED panel, brought online via a manual pinhole reset and paired to the existing Philips Hue Hub rather than directly to HA's radio. That gives a hybrid model — native control still works in the Hue app, and HA's Hue integration mirrors the entity in for automations. The HubZ stick is passed through and ready, but nothing is paired directly to it yet — ZHA/Z-Wave aren't in active use. It's there for the batch of buttons and Zigbee/Z-Wave devices Damien plans to add later.
Net effect: a two-service smart-home foundation is live behind the proxy, the radio stick is passed through, and the first light is controllable from both Hue and Home Assistant.
← Back to Admin Hub