Home Assistant Docker Compose for Homelab 2026: From Container to Smart Home Stack

Reading time: ~14 minutes Audience: Homelab owners who want a self-hosted smart home without vendor lock-in


Introduction: Docker Compose for Home Assistant — Why It Makes Sense in 2026

Home Assistant has grown from a hobbyist project into the most popular open-source smart home platform on the planet. The r/homeassistant subreddit sits at over 250,000 members, and every major smart device manufacturer now either supports Home Assistant directly or has a community integration that works better than the official app. But there is one persistent debate that trips up newcomers: should you run Home Assistant OS (HAOS), Home Assistant Container (Docker), or Home Assistant Core (bare Python)?

If you already run a homelab — Proxmox, a mini PC, or even a Raspberry Pi 5 with a handful of Docker containers — the answer leans strongly toward Docker Compose. Here is why: you already have Docker running for your media server, reverse proxy, DNS filter, and monitoring stack. Adding Home Assistant as another Compose service keeps your infrastructure uniform, your management tooling consistent, and your backup strategy identical across every service.

This guide covers the complete stack: Home Assistant Core in Docker, Mosquitto MQTT, Zigbee2MQTT for USB dongle integration, an optional Node-RED automation sidecar, reverse proxy with Nginx Proxy Manager, remote access via Cloudflare Tunnel, and Proxmox LXC USB passthrough. Every code block is copy-paste ready for 2026.


Why Docker Compose for Home Assistant in a Homelab?

Before writing a single YAML line, you need to understand the tradeoffs. Home Assistant offers three official installation methods, and they are not interchangeable.

HAOS vs Docker Compose vs Core: The Decision Tree

Method What It Is Best For Watch Out For
HAOS Full OS image (rootfs or VM) Raspberry Pi dedicated, VM-only homelab Steals an entire device or VM; can’t run other Docker containers alongside
Docker Compose homeassistant/home-assistant container Homelab with existing Docker stack No add-on store; replace add-ons with separate containers
Home Assistant Core pip install homeassistant Python developers, custom integrations No supervisor; manual OS dependency management

Choose HAOS if: You want a dedicated appliance, do not run other server workloads, and want the one-click add-on store for things like Node-RED, ESPHome, and DuckDNS.

Choose Docker Compose if: You already run Docker for other services (Jellyfin, Nginx Proxy Manager, Pihole, Grafana), you use Portainer or Dockge for container management, and you are comfortable running companion services as separate containers instead of add-ons.

Choose Core if: You are developing custom integrations in Python and need direct filesystem access without container isolation.

The Add-On Question (and the Docker Workaround)

The number-one reason people hesitate to use Docker is the add-on store. In HAOS, add-ons are pre-packaged Docker containers that Home Assistant manages for you — Node-RED, ESPHome, Mosquitto, Zigbee2MQTT, and file editors all install with one click.

In Docker Compose, you simply run each add-on as a separate container in the same Compose stack. The result is identical — Mosquitto still talks to Home Assistant over the internal Docker network; Zigbee2MQTT still publishes to MQTT — but you manage them through docker compose pull && docker compose up -d instead of the Supervisor UI. For homelabbers already managing 20 containers, this is a feature, not a bug: one workflow, one update cadence, no split-brain management.

For the few add-ons that do not have standalone Docker images (like the Google Drive Backup add-on), you use HACS (Home Assistant Community Store) to install equivalent custom integrations directly inside the Home Assistant container — no Supervisor required.


What You’ll Need

Hardware

Tier Hardware RAM Storage Power Best For
Budget Raspberry Pi 5 (8 GB) + 128 GB SSD 8 GB 128 GB SSD 8–12W Starter smart home (lights, sensors, 1–2 cameras)
Standard Intel N100 Mini PC (Beelink S12 Pro, Minisforum UN100) 16 GB 256 GB NVMe 12–18W Full smart home + Frigate NVR + 4–8 cameras
Enthusiast Proxmox VM (4 vCPU, 8 GB RAM) on existing homelab server 8 GB 64 GB virtual disk Shared Consolidation — run HA alongside other VMs/CTs
Power User Intel N305 or AMD Ryzen 5825U Mini PC 32 GB 512 GB NVMe 25–40W Frigate AI object detection, Whisper voice, Matter controller

Zigbee/Z-Wave USB Dongle Recommendations (2026):

  • Sonoff ZBDongle-P (CC2652P) — ~$15, excellent range, Zigbee2MQTT and ZHA compatible
  • ConBee III (RaspBee III for Pi GPIO) — ~$40, deCONZ or Zigbee2MQTT, very mature
  • Home Assistant SkyConnect (EFR32MG21) — ~$30, official Nabu Casa hardware, Thread/Matter ready, ZHA native
  • Aeotec Z-Stick 7 (Z-Wave 700 series) — ~$50, best Z-Wave dongle, 700-series for long range

Software

  • Docker Engine 24+ and Docker Compose v2 (bundled with Docker)
  • Git for version-tracking your Compose files
  • Static IP or mDNS (Avahi) on the host so integrations can discover it reliably

Project Structure

Create a clean directory on your Docker host. Version-control it with Git — your Compose file is infrastructure as code, and you will thank yourself after the first accidental docker compose down.

~/docker/homeassistant/
├── .env                  # Version tags and secrets
├── docker-compose.yml    # Main stack
├── mosquitto/
│   └── config/
│       └── mosquitto.conf
├── zigbee2mqtt/
│   └── data/
│       └── configuration.yaml
├── node-red/
│   └── data/
└── homeassistant/
    └── config/           # Auto-created on first run

The .env file pins exact versions so you control when updates happen:

# .env — Version pins for Home Assistant smart home stack
# Update these manually and review changelogs before bumping
HA_VERSION=2026.6.0
MOSQUITTO_VERSION=2.0.18
Z2M_VERSION=1.37.0
NODE_RED_VERSION=4.0.2
TZ=Asia/Jakarta

Volume strategy: every container gets a bind-mounted directory under ~/docker/homeassistant/. This keeps backups simple — rsync -a ~/docker/homeassistant/ /mnt/nas/backups/homeassistant/ captures your entire smart home config in one command.


The Docker Compose File

Below is the complete docker-compose.yml. Each service is explained after the block.

# docker-compose.yml — Home Assistant Smart Home Stack
# Deploy: docker compose up -d
# Update: docker compose pull && docker compose up -d

services:
  # ── Core: Home Assistant ──────────────────────────────
  homeassistant:
    image: homeassistant/home-assistant:${HA_VERSION}
    container_name: homeassistant
    restart: unless-stopped
    network_mode: host
    privileged: true  # Required for USB device access
    environment:
      - TZ=${TZ}
    volumes:
      - ./homeassistant/config:/config
      - /run/dbus:/run/dbus:ro  # Bluetooth (optional)
    # If using bridge network instead of host, add:
    # ports:
    #   - "8123:8123"
    # and comment out network_mode: host

  # ── MQTT Broker: Mosquitto ────────────────────────────
  mosquitto:
    image: eclipse-mosquitto:${MOSQUITTO_VERSION}
    container_name: mosquitto
    restart: unless-stopped
    ports:
      - "1883:1883"   # MQTT
      - "9001:9001"   # WebSockets
    volumes:
      - ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
      - mosquitto_data:/mosquitto/data
      - mosquitto_log:/mosquitto/log

  # ── Zigbee Bridge: Zigbee2MQTT ────────────────────────
  zigbee2mqtt:
    image: koenkk/zigbee2mqtt:${Z2M_VERSION}
    container_name: zigbee2mqtt
    restart: unless-stopped
    depends_on:
      - mosquitto
    devices:
      - /dev/serial/by-id/usb-Silicon_Labs_CP2102N_USB_to_UART_Bridge_Controller_XXXXXXXX:/dev/ttyUSB0
    volumes:
      - ./zigbee2mqtt/data:/app/data
    environment:
      - TZ=${TZ}
    # network_mode: host  # Uncomment if discovery fails on bridge

  # ── Optional: Node-RED Automation ─────────────────────
  node-red:
    image: nodered/node-red:${NODE_RED_VERSION}
    container_name: node-red
    restart: unless-stopped
    ports:
      - "1880:1880"
    volumes:
      - ./node-red/data:/data
    environment:
      - TZ=${TZ}

volumes:
  mosquitto_data:
  mosquitto_log:

Service-by-Service Explanation

Home Assistant (network_mode: host): Using host networking is the officially recommended approach for the Docker container because Home Assistant relies on mDNS (Bonjour/Avahi), UPnP, and DHCP discovery to find devices on your LAN. Without host mode, many auto-discovered integrations (Google Cast, Apple HomeKit, Philips Hue, Sonos) silently break. The privileged: true flag gives the container access to USB devices — essential for Zigbee/Z-Wave dongles. If your security policy forbids host networking, use a bridge network with explicit port mappings and accept that device discovery will be manual.

Mosquitto (bridge network): This is a standard MQTT broker. It runs on the default bridge network so Home Assistant can reach it at mosquitto:1883 (Docker DNS) if needed, but since Home Assistant is on host mode, you configure the MQTT integration with localhost:1883 instead. The bridge mode keeps Mosquitto isolated while still being reachable from the LAN for non-Docker MQTT clients.

Zigbee2MQTT (bridge with device passthrough): The devices: block maps the physical USB dongle into the container. Find your dongle’s stable path with ls -la /dev/serial/by-id/ — this symlink survives reboots, unlike /dev/ttyUSB0 which can change numbering.

Node-RED (bridge network, optional): If you prefer visual flow-based automation over Home Assistant’s built-in YAML automations, Node-RED is the gold standard. It connects to Home Assistant via the Home Assistant WebSocket API and to MQTT via the broker.

Mosquitto Configuration

Create ./mosquitto/config/mosquitto.conf:

listener 1883
allow_anonymous false
password_file /mosquitto/config/passwd
persistence true
persistence_location /mosquitto/data
log_dest stdout

Set the MQTT credentials (run this after the first docker compose up -d mosquitto):

docker compose exec mosquitto mosquitto_passwd -c /mosquitto/config/passwd homeassistant

Use these credentials when configuring the MQTT integration in Home Assistant.

Zigbee2MQTT Configuration

Create ./zigbee2mqtt/data/configuration.yaml:

homeassistant: true
permit_join: false  # Enable only when pairing new devices
mqtt:
  server: mqtt://localhost:1883
  user: homeassistant
  password: your_password_here
serial:
  port: /dev/ttyUSB0
  adapter: zstack  # For Sonoff ZBDongle-P. Use 'deconz' for ConBee
frontend:
  port: 8080
advanced:
  network_key: GENERATE_A_RANDOM_KEY

Generate the network_key with openssl rand -hex 16 — this encrypts your Zigbee network and must be the same if you ever re-pair devices. Save it somewhere safe.


Step-by-Step Installation

Step 1 — Prepare Your Host

On Proxmox (LXC container): Create an unprivileged LXC container with Debian 12, enable nesting=1 and keyctl=1 in the container options. Install Docker inside the LXC. For USB passthrough, add the LXC device mapping in Proxmox: select the container → Resources → Add → Device Passthrough → /dev/serial/by-id/usb-Silicon_Labs_... — no reboot needed. If you haven’t set up Proxmox yet, see our Proxmox beginner guide.

On Ubuntu/Debian (bare metal or VM):

# Install Docker (official repo method, 2026)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker

# Verify
docker --version
docker compose version

On Raspberry Pi 5:

# Same Docker install, then verify ARM architecture
docker info | grep Architecture  # Should show aarch64

Pi users: Use an SSD via USB 3.0 or NVMe HAT, not a microSD card. Home Assistant writes recorder data continuously; an SD card will fail within 6–12 months under that write load.

Step 2 — Create Directories and Set Permissions

mkdir -p ~/docker/homeassistant/{homeassistant/config,mosquitto/config,zigbee2mqtt/data,node-red/data}
chown -R 1000:1000 ~/docker/homeassistant/node-red/data

Node-RED runs as UID 1000 by default. Home Assistant and Zigbee2MQTT create their own directory structures on first run.

Step 3 — Write the Docker Compose File

Copy the docker-compose.yml and .env from the sections above into ~/docker/homeassistant/. Adjust your Zigbee dongle path under devices: in the Zigbee2MQTT service. If you don’t have a Zigbee dongle yet, comment out the entire zigbee2mqtt service — you can add it later without rebuilding the stack.

Step 4 — Start the Stack

cd ~/docker/homeassistant
docker compose up -d

# Watch logs to confirm startup
docker compose logs -f homeassistant

Wait 2–3 minutes for the first-run initialization. Home Assistant downloads several Python wheels and builds the frontend on first start. You will know it’s ready when the logs show "Home Assistant initialized in X seconds".

Step 5 — First-Run Configuration

Open http://<host-ip>:8123 in a browser. Home Assistant walks you through:

  1. Create owner account — This is your admin user. Use a strong password.
  2. Name your home — Pick the location for timezone, sunrise/sunset, and weather automations.
  3. Device discovery — Home Assistant auto-scans for devices on your network (printers, Chromecasts, Roku, Philips Hue bridges). Accept or skip.
  4. Integrations — You will be prompted to install discovered integrations. At minimum, install MQTT and point it to localhost:1883 with the credentials you set in Mosquitto.

Step 6 — Install HACS (Home Assistant Community Store)

HACS is the Docker user’s replacement for the add-on store. It gives you access to thousands of custom integrations and Lovelace dashboard themes — install once, then browse and one-click install forever.

docker compose exec homeassistant bash
wget -O - https://get.hacs.xyz | bash -
exit
docker compose restart homeassistant

After restart, go to Settings → Devices & Services → Add Integration → HACS. You will need a free GitHub account to authenticate. Once HACS is installed, use it to add integrations like Frigate (NVR), Adaptive Lighting, and Browser Mod.

Step 7 — Add Zigbee2MQTT Integration

After the Zigbee2MQTT container is running (check at http://<host-ip>:8080), enable the frontend, then in Home Assistant go to Settings → Devices & Services → Add Integration → MQTT. Home Assistant auto-discovers the Zigbee2MQTT bridge and all paired devices appear automatically.

To pair your first Zigbee device: set permit_join: true in zigbee2mqtt/data/configuration.yaml, restart the container (docker compose restart zigbee2mqtt), then put your device in pairing mode (usually hold a button for 5 seconds). The device appears in the Home Assistant dashboard within seconds. Disable permit_join immediately afterward.

Step 8 — Backup Strategy

Your entire configuration lives in ~/docker/homeassistant/. Back it up with:

# Stop containers to ensure consistent backups
cd ~/docker/homeassistant && docker compose stop
rsync -a ~/docker/homeassistant/ /mnt/nas/backups/homeassistant/$(date +%Y-%m-%d)/
docker compose up -d

Automate this with a cron job or a Proxmox Backup Server job (if your Docker host is a Proxmox VM/LXC). The Home Assistant container also has a built-in backup service accessible at Settings → System → Backups — use it to create snapshots that include add-on and integration data.


Reverse Proxy & Remote Access

Exposing Home Assistant directly on port 8123 to the internet is a bad idea. Use a reverse proxy — it gives you HTTPS, path-based routing, and access control in one place.

Option 1: Nginx Proxy Manager (Recommended)

If you already use Nginx Proxy Manager (and you should — see our NPM Docker Compose guide), adding Home Assistant is a 30-second task:

  1. In NPM, add a new Proxy Host.
  2. Domain: home.example.com (or whatever subdomain you use).
  3. Forward Hostname/IP: <host-ip> and Port: 8123.
  4. Enable WebSocket Support (critical — Home Assistant uses WebSockets for real-time state updates).
  5. Request an SSL certificate via Let’s Encrypt.

That is it. Your Home Assistant is now accessible at https://home.example.com with automatic HTTPS renewal.

Option 2: Cloudflare Tunnel (No Port Forwarding)

If your ISP uses CGNAT or you want zero open ports, use Cloudflare Tunnel. See our Cloudflare Tunnel homelab guide for the full setup, but the Home Assistant-specific config looks like:

# In your cloudflared config.yml
tunnel: <tunnel-id>
credentials-file: /etc/cloudflared/<tunnel-id>.json

ingress:
  - hostname: home.example.com
    service: http://<host-ip>:8123
  - service: http_status:404

Cloudflare Tunnel wraps your Home Assistant in Cloudflare’s CDN and DDoS protection. Bonus: you get free analytics on who accesses your smart home (and from where).

Option 3: Tailscale Mesh VPN

For maximum security with zero exposed ports, install Tailscale on both your Docker host and your phone/laptop. Access Home Assistant at http://<tailscale-hostname>:8123 from anywhere. No reverse proxy, no DNS records, no TLS certificates needed. Combine this with Tailscale’s subnet routing to access your entire LAN remotely through one node.


Zigbee/Z-Wave USB Dongle Passthrough

This is the section that trips up most Docker users. Passing a USB device into a container is straightforward on bare metal and slightly more involved on Proxmox LXC.

Bare Metal / VM Host

Use the devices: block in docker-compose.yml as shown above. Find your dongle’s stable symlink:

ls -la /dev/serial/by-id/
# Output: usb-Silicon_Labs_CP2102N_USB_to_UART_Bridge_Controller_XXXXXXXX -> ../../ttyUSB0

Use the /dev/serial/by-id/... path in your Compose file — it survives USB port changes and reboots.

Permission fix: If Zigbee2MQTT logs show "Error: cannot open /dev/ttyUSB0":

sudo usermod -aG dialout $USER
# Log out and back in, or reboot

The privileged: true flag on the Home Assistant container usually handles this, but the Zigbee2MQTT container needs the dialout group access on the host.

Proxmox LXC USB Passthrough

In the Proxmox web UI: 1. Select your LXC container → ResourcesAddDevice Passthrough. 2. Choose /dev/serial/by-id/usb-Silicon_Labs_... from the dropdown. 3. Restart the LXC.

Inside the LXC, the device appears at the same path. No container-level devices: block is needed — the Proxmox host-level passthrough supersedes it.

Common issue: If the device does not appear inside the LXC, check that the LXC is privileged (not recommended for security) or add the device’s cgroup permissions in /etc/pve/lxc/<container-id>.conf:

lxc.cgroup2.devices.allow: c 188:* rwm
lxc.mount.entry: /dev/serial/by-id/usb-Silicon_Labs_CP2102N_USB_to_UART_Bridge_Controller_XXXXXXXX dev/serial/by-id/usb-Silicon_Labs_CP2102N_USB_to_UART_Bridge_Controller_XXXXXXXX none bind,optional,create=file

Updating & Maintenance

Docker Compose Update Workflow

Home Assistant releases a new version every month. Update your stack with:

cd ~/docker/homeassistant

# Pull new images (respects version pins in .env)
docker compose pull

# Recreate containers with new images
docker compose up -d

# Remove old dangling images
docker image prune -f

Version Pinning Strategy

The .env file pins major.minor.patch versions. Never use the latest tag — Home Assistant introduces breaking changes in minor releases (e.g., a Python version bump, an integration deprecation). Before bumping a version:

  1. Read the Home Assistant release notes.
  2. Check the Breaking Changes section.
  3. Review your active integrations for deprecation warnings.
  4. Bump the version in .env, pull, and restart.

Portainer-Managed Updates

If you use Portainer (and you should — see our Portainer Docker management guide), you can manage the entire Home Assistant stack through the Portainer UI. Add the stack via Stacks → Add Stack → Web Editor, paste your Compose file, and deploy. Portainer shows container logs, resource usage, and update status — useful when you are on mobile and need to restart Home Assistant without SSH.


Troubleshooting Common Issues

Home Assistant Not Discovering Devices (mDNS/DLNA/UPnP)

Symptom: Google Cast, Sonos, Philips Hue, or Apple HomeKit devices do not appear in auto-discovery.

Fix: Verify network_mode: host is set on the homeassistant service. If you must use bridge mode for security reasons, you lose auto-discovery — add devices manually by IP address in the integration settings.

Zigbee2MQTT Cannot Access USB Dongle

Symptom: Logs show "Error: Error: cannot open /dev/ttyUSB0 (Permission denied)".

Fix: On the host: sudo usermod -aG dialout $USER && newgrp docker. Also verify the device path with ls /dev/serial/by-id/ — the symlink may have changed after a kernel update.

Container Not Starting After Reboot

Symptom: After a host reboot, docker compose ps shows containers as exited.

Fix: Ensure restart: unless-stopped is set on every service. If containers still do not auto-start, check that Docker itself starts on boot: sudo systemctl enable docker.

Database Corruption Recovery

Symptom: Home Assistant starts but history/logbook pages are blank.

Fix: The default SQLite database can corrupt after unexpected power loss. Purge it:

docker compose stop homeassistant
rm ~/docker/homeassistant/homeassistant/config/home-assistant_v2.db
docker compose start homeassistant

You lose history data (entity states, logbook entries) but your configuration (automations, scripts, dashboards) is unaffected — those live in separate YAML files. Consider switching to a MariaDB or PostgreSQL recorder for production setups.


FAQ: Docker Compose vs HAOS vs Core

Q: Can I use add-ons with Docker? No. Add-ons are exclusive to HAOS and Supervisor. In Docker, you run each add-on as a separate container in the same Compose stack. This is a lateral move: Mosquitto as a container works identically to the Mosquitto add-on; it just appears in Portainer instead of the Supervisor UI. For add-ons that do not have standalone Docker images, use HACS custom integrations.

Q: Is Docker less stable than HAOS? No. Under the hood, HAOS runs the same homeassistant/home-assistant Docker image. The Supervisor is a separate container that manages add-ons and snapshots — it does not affect core stability. The stability difference is zero. What changes is who manages the companion services: you (Docker) or the Supervisor (HAOS).

Q: Should I run Home Assistant in a VM instead? If you want HAOS features (add-ons, one-click backups, Google Drive backup) without dedicating a physical machine, a Proxmox VM running HAOS is an excellent middle ground. You get the full HAOS experience while your other Docker containers run in separate VMs or LXCs on the same Proxmox host. The downside: you reserve RAM and CPU for the VM instead of sharing via containers.

Q: How do I back up Home Assistant in Docker? Two methods: (1) rsync the entire ~/docker/homeassistant/ directory while containers are stopped — this captures everything including your Compose file, MQTT config, and Zigbee2MQTT data. (2) Use Home Assistant’s built-in backup tool at Settings → System → Backups to create a .tar snapshot, then copy it off-host with SCP or rsync. Either method works; method 1 is more complete because it captures non-HA data too.

Q: Can I run Home Assistant on a Raspberry Pi 5 with Docker? Yes, and it works well — with one caveat. Use an NVMe SSD (via a HAT or USB 3.0 enclosure), not a microSD card. Home Assistant’s recorder writes to the database continuously; SD cards wear out in 6–12 months under that load. With an SSD, a Raspberry Pi 5 (8 GB) handles a modest smart home of 20–30 Zigbee devices and a few Wi-Fi integrations without breaking a sweat.


Conclusion: Your Smart Home, Your Rules

Running Home Assistant with Docker Compose gives you the best of both worlds: the power and flexibility of the world’s best open-source smart home platform, plus the infrastructure consistency of your existing homelab stack. You manage Home Assistant the same way you manage Jellyfin, Pihole, and Nginx Proxy Manager — one Compose file, one update workflow, one backup strategy.

The tradeoff — no add-on store — is smaller than it appears. HACS fills the integration gap, and running companion services as separate containers gives you finer control over versions, resource limits, and networking. For a homelabber already comfortable with Docker, it is the natural choice.

Next steps from here:

Happy automating — and remember: permit_join: false.