Home Assistant on a NAS: Why Placement Beats Hardware

Where should Home Assistant live — the beefy cluster or the home NAS? Discovery and USB radios don't cross a VPN tunnel, so site beats specs. Here's the build.

On this page
  1. The question everyone gets backwards
  2. Container or the full appliance?
  3. The Compose file I actually shipped
  4. Reaching it from anywhere without opening a port
  5. Wire it into monitoring on day one
  6. What’s next: voice, without selling your privacy

I spent a while overthinking where to run Home Assistant. I have a four-node Proxmox cluster sitting there with cores to spare, and a NAS that is comparatively modest. Every instinct said “put the smart-home brain on the powerful machine.” That instinct is wrong, and it took me one honest look at how discovery works to see why. The machine’s specs barely matter. What matters is which building it is in.

Site beats hardwareHome Assistant has to sit on the same LAN as the devices — not just the fastest box.HOME · your LANbulbsensorZigbee radioNASHomeAssistantSame LAN → discovery + USB radios reach itTailscale tunnelremote UI ✓ crossesdevice discovery ✕mDNS · SSDP · USBOFFICE · another siteProxmox clusterbeefy, 4 nodes — wrong site

This guide is the counterpart to running Home Assistant OS in a Proxmox VM: same question, opposite answer, because the deciding factor is not the hypervisor — it is the postcode. I will walk through how I placed it, the Docker Compose file I actually shipped, how I reach it from my phone without opening a single port, and how I wired it into monitoring the same afternoon.


The question everyone gets backwards

The usual way people choose where to run Home Assistant is by counting cores and RAM. That is the wrong criterion. Home Assistant’s job is to find and talk to your devices, and the protocols it uses to do that are deliberately local:

  • mDNS (multicast DNS) is how a Chromecast, a printer, or an ESPHome sensor announces itself. It is a link-local protocol by design — the RFC that defines it uses a multicast address scoped to the local network, so routers do not forward it and it does not survive a hop across a VPN.
  • SSDP (the discovery half of UPnP) works the same way: a local multicast shout that stays on the LAN.
  • Local-push integrations expect the device and Home Assistant to be on the same subnet.
  • USB radios for Zigbee and Z-Wave are physical. The stick has to be plugged into the machine running Home Assistant. You cannot plug a USB dongle into a server in another building.

A Tailscale tunnel will happily carry your dashboard between sites — you can sit at the office and toggle a light at home. What it will not carry is that local discovery multicast or a USB serial port. So the moment your cluster and your devices are in different places, the cluster is disqualified as the host no matter how fast it is.

My layout made the decision for me: the cluster is at the office; the NAS and every smart device are at the house. Here is the whole decision as a table.

Where Home Assistant runs Discovery + USB radios Verdict
Home NAS (with the devices) Works — same LAN, sticks plug in locally ✅ Run it here
Offsite Proxmox cluster Broken — multicast and USB don’t cross the tunnel ❌ Powerful but blind
Wait for a future home cluster Would work, but doesn’t exist yet ⏳ Migrate later; HA’s own backup covers the move

The NAS also had the room to spare — port 8123 was free and there was roughly 10 GB of RAM available — so this was not a case of squeezing onto a maxed-out box. It was the right box because of where it sits.

The one-line rule

If a device needs to be discovered or has a radio, Home Assistant belongs on the same LAN as that device. Compute is the tiebreaker, never the deciding vote.


Container or the full appliance?

Once the NAS won, the next fork was how to install it. Home Assistant has several official install methods, and the two that matter here are Home Assistant Operating System (HAOS) and Home Assistant Container.

The official FAQ is blunt about the trade: Home Assistant Container is the plain Docker install of Home Assistant Core — no Supervisor, no add-on store. HAOS is the full appliance that does include the Supervisor and the add-on store, and it is happiest in its own dedicated VM — which is exactly what the HAOS-in-a-VM approach is for.

Losing the add-on store sounds like a downgrade until you notice what you get in return. On HAOS, things like your MQTT broker are add-ons managed by the Supervisor. On Container, each companion service is its own_ Docker container sitting in the same Compose stack the NAS already runs. That separation is a feature: restarting Home Assistant to load a new integration does not take your Zigbee network down with it, because Zigbee2MQTT is a different container that never restarted.

An HA restart shouldn’t drop your Zigbee meshCompanion services are separate containers — the restart’s blast radius is one box.NAS · docker-compose stackrestart blast radiusHomeAssistantrestarting…MosquittoMQTT broker · stays upZigbee2MQTTowns the radio · stays upreconnectsZigbee mesh stays onlineOn Home Assistant OS the same pieces are Supervisor-managed add-ons instead of your own compose services.

That is the trade in one sentence: you give up the one-click add-on store, and you get a smart-home hub whose blast radius on restart is exactly one container. For a NAS that already speaks Docker Compose, that is the better deal.


The Compose file I actually shipped

Here is the real shape of what runs, cleaned of my own addresses. The three lines that carry all the reasoning are image, network_mode, and restart.

First: make these values your own

Everything below is an example — swap in your own before you copy it:

  • /volume1/docker/homeassistant/config → wherever your NAS keeps container config (Synology and Asustor use a /volume1 root; yours may differ).
  • TZ: America/Chicago → your timezone.
  • 2026.8.1 in the image tag → whatever the current stable release is when you deploy.
  • Any IP or hostname you see later (10.0.0.10, homelab-nas) is a stand-in for your own.

Rule of thumb: if a value looks specific to one machine, it’s a placeholder to change — not a literal to copy.

1Write the compose file5 min

Home Assistant’s own container docs use the official image at ghcr.io/home-assistant/home-assistant. I pin it to an exact version rather than :stable so an unattended pull never surprises me with a breaking release.

docker-compose.yml — Home Assistant Container on a NAS

services:
homeassistant:
  container_name: homeassistant
  image: ghcr.io/home-assistant/home-assistant:2026.8.1
  network_mode: host          # required for mDNS/SSDP discovery
  restart: unless-stopped     # (I use "always" on the NAS — see below)
  environment:
    TZ: America/Chicago
  volumes:
    - /volume1/docker/homeassistant/config:/config
    - /etc/localtime:/etc/localtime:ro
  # Attaching a USB Zigbee/Z-Wave stick later? Add these then:
  # privileged: true
  # devices:
  #   - /dev/serial/by-id/usb-YOUR_COORDINATOR-if00:/dev/ttyUSB0

# Companion services stay commented until the hardware or credentials
# exist. Nothing dead ships live — but each is its own container, so an
# HA restart never drops the Zigbee mesh:
# mosquitto:      # MQTT broker  -> https://mosquitto.org
# zigbee2mqtt:    # needs a USB Zigbee coordinator
# zwave-js-ui:    # needs a Z-Wave stick -> https://github.com/zwave-js/zwave-js-ui
# matter-server:  # Matter controller
2Understand the three load-bearing lines3 min
  • image: …:2026.8.1 — a pinned tag. Home Assistant ships a new version roughly every month, and those releases occasionally break things (2026.8, for example, started requiring UniFi Protect 7.1+). Pinning means you decide when to bump, after reading the release notes — the same discipline you would apply to any other production container.
  • network_mode: host — the non-negotiable one. Home Assistant recommends host networking precisely so its discovery can reach the LAN. In a bridged network the multicast never arrives and integrations quietly find nothing. (The catch: host mode means you reach other containers by localhost:port, not by container name — the same Docker localhost trap that bites a lot of first stacks.)
  • restartunless-stopped is the polite default that respects a manual stop. On the NAS I actually set restart: always, because after a power blip I want the house’s brain back no matter what. Pick based on whether you ever want a manual stop to stick across a reboot.
3Bring it up and claim it immediately5 min
Start the container and watch the first boot

docker compose up -d
docker compose logs -f homeassistant

Open http://10.0.0.10:8123 (your NAS’s LAN address) and finish onboarding. Do this now, not later.

An unclaimed Home Assistant is anyone's to claim

A fresh install is an open onboarding page. Until you create the owner account, anyone who can reach that address on your LAN or tunnel can claim it and become the admin. Claim it the same day you deploy it — I did, and it is the cheapest security decision in this whole guide.

If you later lose that password, you do not have to start over: Home Assistant has an official offline reset (hass --script auth … change_password) that keeps your account and — importantly — your long-lived tokens. That is a full walkthrough in its own right, so it gets its own playbook.


Reaching it from anywhere without opening a port

I never expose Home Assistant to the internet. There is no port forward and no public hostname — the same posture I use for every other private service. Remote access rides on Tailscale instead, and this is the part that surprises people: the dashboard crosses the tunnel just fine, even though discovery never could.

How I reach it Address
On the home LAN http://10.0.0.10:8123
Over the tailnet (laptop, phone) http://homelab-nas:8123

On my phone, the official Home Assistant iOS companion app points at the tailnet address and doubles as presence detection — it tells the house when I have come home. There is no official Home Assistant desktop app for Linux, so on the laptop I just open the web UI (in my case, through a dedicated tab in my homelab app, but any browser works).

If your smart devices worry you — and a cheap smart plug probably should — this pairs naturally with putting them on their own VLAN so your smart TV can’t see your NAS. Home Assistant bridges the segments deliberately; the devices don’t roam your whole network.


Wire it into monitoring on day one

The mistake I have made before is standing up a service and only noticing it died a week later. So the same afternoon I deployed Home Assistant, I gave it three monitoring hooks before I added a single automation.

1An uptime check2 min

The lightest possible net: an Uptime Kuma monitor hitting http://10.0.0.10:8123 on a 60-second interval. If the container falls over, I know in a minute.

2A dashboard tile2 min

An entry on my Homepage dashboard so Home Assistant lives next to everything else I run, one click away.

3Metrics into Prometheus10 min

Home Assistant has a first-party Prometheus integration. You enable it with a single line in configuration.yaml, and it exposes everything at /api/prometheus behind a bearer token.

configuration.yaml — enable the metrics endpoint

prometheus:

Then point Prometheus at /api/prometheus with a long-lived access token (the endpoint returns 401 without one). After a restart I had the scrape live and just over a hundred metric series flowing.

There's a networking story hiding here

Getting that scrape to actually connect — from a monitoring box on a different site, across a subnet router, past a Tailscale ACL and the NAS firewall — turned into a three-layer debugging session that deserves its own post. This guide is the happy path; the diagnosis is the sequel.


What’s next: voice, without selling your privacy

The last thing on my list is voice, and it is the cleanest illustration of the whole “stay local” theme. The free, manual Alexa and Google integrations both require a public HTTPS endpoint for Home Assistant — exactly the internet exposure I have been avoiding. So those are out.

The two paths that respect the posture:

  • A local voice pipeline. Home Assistant’s Assist already does text commands today. For spoken control you add speech-to-text and text-to-speech as their own Wyoming-protocol containers (Whisper and Piper) — which, on a Container install, is just two more services in the Compose stack. No cloud, no exposure.
  • Home Assistant Cloud. The paid subscription from Nabu Casa (which also funds the project) handles the remote link and cloud voice for you without opening your instance to the world. Check their site for the current price.

Both are honest options. I am leaning local, because “two more containers on a stack I already run” is a very small ask for keeping my house off someone else’s server.


Related posts:


Recommended hardware for this setup:

  • Zigbee USB coordinator — the stick that has to plug into the machine running Home Assistant, which is the whole reason it lives on the NAS
  • Z-Wave USB controller — same story for Z-Wave devices
  • USB extension cable — get the radio away from the NAS chassis; USB 3.0 and metal enclosures are notorious for jamming 2.4 GHz Zigbee

This post contains Amazon affiliate links (tag: buildahomelab-20). I earn a small commission on qualifying purchases at no extra cost to you.

Comments

Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.