Skip to content
Go back

Home Assistant Add-Ons vs Docker Containers

By SumGuy 10 min read
Home Assistant Add-Ons vs Docker Containers

You Picked the Wrong Install Method and Now You’re Paying for It

Here’s a scenario that plays out in home lab forums about three times a week: someone sets up Home Assistant Container because they already have Portainer, Traefik, and thirty other services humming along in Docker Compose. Clean, minimal, no magic. Then they try to add Frigate, or the Mosquitto broker, or ESPHome — and they hit the wall.

“Where’s the Add-On store?”

It’s not there. The Add-On store is a Supervisor feature, and Supervisor is not part of HA Container. So now you’re either rebuilding your entire setup on HAOS, or you’re wiring up Mosquitto and Zigbee2MQTT yourself like some kind of responsible adult who reads documentation.

Neither answer is obviously wrong. But you need to understand what you signed up for — ideally before you’re three hours into a Sunday afternoon that was supposed to be quick.


The Four Flavors — Actually Read This Part

Home Assistant ships as four distinct installation methods, and the community does a terrible job making this clear on first encounter.

HAOS (Home Assistant Operating System) — A full Linux OS image. Boots straight into HA. The Supervisor orchestrates everything: Add-Ons, snapshots, OS updates, the whole appliance. Runs on a Pi, an x86 mini-PC, or a VM. This is what the official docs point you at, and for good reason.

HA Supervised — The Supervisor daemon running on your own Debian install. You get the Add-On store and the backup magic, but you’re responsible for the underlying OS. The officially supported OS is Debian (specific version, no deviations). Deviate, and the UI will passive-aggressively flag you as “unsupported” forever.

HA Container — Just the Home Assistant Core process in a Docker container. No Supervisor. No Add-On store. No built-in backup orchestration. You bring your own infrastructure.

HA Core — A Python virtualenv install. Nobody should be doing this manually in 2026 unless they have a very specific reason.

The practical choice is almost always HAOS vs Container. Supervised exists in a weird middle ground that requires Debian discipline most home labbers won’t maintain, and Core is for edge cases.


What Add-Ons Actually Are

Add-Ons are Docker containers. That’s it. The Supervisor pulls an OCI image, starts it, and wires it into a shared networking namespace with Home Assistant. The Add-On store is essentially a curated container registry with a point-and-click interface and opinionated defaults.

When you install the Mosquitto broker Add-On, you’re getting a container running eclipse-mosquitto with a config file the Supervisor manages, credentials automatically shared with HA, and a log viewer baked into the UI. When you click “Update,” Supervisor pulls the new image, stops the old one, and starts the new one. When you take a snapshot, it includes Mosquitto’s config and data.

That’s genuinely nice. The friction is near zero for a standard setup.

The trade-off is control. You’re not editing mosquitto.conf directly — you’re going through the Add-On’s configuration panel, which exposes the options the maintainer decided to surface. If you want per_listener_settings true and it’s not in the panel, you’re either finding a workaround or living without it.


The Compose Stack You’d Build Anyway

If you’re running HA Container, you’re already comfortable with Compose files. Here’s the full stack for the services people usually reach for Add-Ons to cover.

Home Assistant Core

docker-compose.yml
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: homeassistant
restart: unless-stopped
privileged: true
network_mode: host
volumes:
- ./config:/config
- /etc/localtime:/etc/localtime:ro
environment:
- TZ=America/Chicago

network_mode: host is not laziness — HA uses mDNS, SSDP, and broadcast discovery for device detection. Bridge networking breaks a surprising number of integrations.

Mosquitto MQTT Broker

mosquitto:
image: eclipse-mosquitto:2
container_name: mosquitto
restart: unless-stopped
ports:
- "1883:1883"
- "9001:9001"
volumes:
- ./mosquitto/config:/mosquitto/config
- ./mosquitto/data:/mosquitto/data
- ./mosquitto/log:/mosquitto/log
mosquitto/config/mosquitto.conf
listener 1883
listener 9001
protocol websockets
allow_anonymous false
password_file /mosquitto/config/passwd
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

Create your password file before starting:

Terminal window
docker run --rm -it \
-v $(pwd)/mosquitto/config:/mosquitto/config \
eclipse-mosquitto:2 \
mosquitto_passwd -c /mosquitto/config/passwd youruser

Zigbee2MQTT

zigbee2mqtt:
image: koenkk/zigbee2mqtt:latest
container_name: zigbee2mqtt
restart: unless-stopped
volumes:
- ./zigbee2mqtt/data:/app/data
- /run/udev:/run/udev:ro
ports:
- "8080:8080"
environment:
- TZ=America/Chicago
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
depends_on:
- mosquitto
zigbee2mqtt/data/configuration.yaml
homeassistant: true
permit_join: false
mqtt:
base_topic: zigbee2mqtt
server: mqtt://mosquitto:1883
user: youruser
password: yourpassword
serial:
port: /dev/ttyUSB0
frontend:
port: 8080
advanced:
log_level: info

ESPHome

esphome:
image: ghcr.io/esphome/esphome:stable
container_name: esphome
restart: unless-stopped
volumes:
- ./esphome/config:/config
- /etc/localtime:/etc/localtime:ro
ports:
- "6052:6052"
network_mode: host

ESPHome also benefits from host networking for mDNS device discovery. If you’re flashing over USB, map the device through. If you’re doing OTA only, you can get away without it.

Frigate NVR

frigate:
image: ghcr.io/blakeblackshear/frigate:stable
container_name: frigate
restart: unless-stopped
privileged: true
shm_size: "256mb"
devices:
- /dev/bus/usb:/dev/bus/usb # Coral USB
- /dev/dri/renderD128:/dev/dri/renderD128 # iGPU for decoding
volumes:
- /etc/localtime:/etc/localtime:ro
- ./frigate/config:/config
- ./frigate/media:/media/frigate
- type: tmpfs
target: /tmp/cache
tmpfs:
size: 1000000000
ports:
- "5000:5000"
- "8554:8554" # RTSP
- "8555:8555/tcp" # WebRTC
- "8555:8555/udp"
environment:
- FRIGATE_RTSP_PASSWORD=yourpassword
depends_on:
- mosquitto

Node-RED

node-red:
image: nodered/node-red:latest
container_name: node-red
restart: unless-stopped
ports:
- "1880:1880"
volumes:
- ./node-red/data:/data
environment:
- TZ=America/Chicago
depends_on:
- homeassistant
- mosquitto

All of this in one docker-compose.yml. Single docker compose up -d and you’re running the same stack that would take you fifteen minutes of clicking through Add-On store pages on HAOS. The difference: you built it, you understand it, and mosquitto.conf says exactly what you told it to say.


Where HAOS Wins Without Argument

Backups

The Supervisor backup is genuinely impressive. One click, full snapshot: HA config, Add-On data, custom components, everything. Restores to bare metal. Takes two minutes to move your entire HA instance to new hardware.

With Container, you’re rolling your own backup strategy. Something like:

Terminal window
#!/bin/bash
BACKUP_DIR=/mnt/nas/homeassistant-backups
DATE=$(date +%Y%m%d-%H%M)
# Stop services gracefully
docker compose stop
# Archive the whole stack
tar -czf "$BACKUP_DIR/ha-stack-$DATE.tar.gz" \
./config \
./mosquitto \
./zigbee2mqtt \
./esphome \
./frigate/config \
./node-red/data
docker compose start
echo "Backup complete: ha-stack-$DATE.tar.gz"

This works. It’s just not magic. You’re also responsible for rotating old backups, testing restores, and remembering that Frigate’s media directory is probably not worth backing up at full fidelity.

OS Updates and Watchdog

HAOS handles host OS updates, kernel patches, and restarts through the Supervisor. The system is self-contained. For a Pi sitting in your wiring closet that you want to forget about, this is the correct approach.

Container on a shared Debian host means you’re also managing that host’s apt updates, kernel reboots, and whatever else is running on the same box. More flexibility, more surface area.

Add-On Ecosystem is Fast

The official Add-On store and community repos (via HACS Addons, for instance) ship updates quickly and the UX is genuinely zero-effort. For Zigbee2MQTT specifically, the Add-On ships configuration.yaml changes in a UI panel. For a non-technical household member trying to add a device, this matters.


Where Container Wins

You’re Already Running 30 Services

If Traefik, Vaultwarden, Immich, Jellyfin, and Gitea are all in your Compose stack, adding HA as one more service is trivial. You already have Traefik routing HTTPS, you already have a backup strategy, you already understand container lifecycle.

Adding HAOS would mean a separate machine (or VM) with its own networking, its own update cycle, its own backup destination. The integration overhead often outweighs the Add-On convenience.

Resource Efficiency

HAOS carries the full OS layer, the Supervisor daemon, and the Docker runtime inside the appliance. On a Pi 4 with 4GB RAM, it’s fine. On a 2GB Pi or a resource-constrained VM, the Supervisor overhead matters.

HA Container is lean. The home-assistant container on its own uses around 300-400MB RAM at idle. No Supervisor, no watchdog, no OS layer you don’t control.

Upstream Container Control

When Mosquitto ships a security patch, you decide when you update. docker compose pull && docker compose up -d. The Add-On store ties you to the Add-On maintainer’s release cadence — usually fast, occasionally not.

If you’re running a modified Frigate config with custom model weights or a pinned version for stability, Container gives you exactly that.


HACS Is Orthogonal to All of This

Quick clarification because this trips people up: HACS (Home Assistant Community Store) handles custom integrations and frontend cards — Mushroom Cards, browser_mod, custom components. It installs into the HA config directory and works on any install method, including Container.

HACS is not the Add-On store. You can run HACS on HA Container and still manually manage Mosquitto in Compose. These are completely separate systems. Install HACS regardless of your install method if you want the community integration ecosystem.


Migration Paths

HAOS → Container

If you’re moving from HAOS to a Compose setup, the HA config is portable. The database (home-assistant_v2.db) and your configuration.yaml, automations.yaml, scripts.yaml all move cleanly.

  1. Take a full HAOS snapshot from the UI
  2. Extract the .tar — inside is a homeassistant.tar.gz with your config directory
  3. Drop that content into ./config/ in your Compose setup
  4. Migrate Add-On data manually: Mosquitto passwords, Zigbee2MQTT database.db, Node-RED flows
  5. Start HA Container, verify your history loaded, re-pair any integrations that need device discovery

The recorder database transfers intact. Your 90-day history comes with you.

Container → HAOS

Reverse it. Copy your configuration.yaml and related files into a fresh HAOS install, restore the recorder DB, reinstall integrations. The Add-On configs start fresh — you’ll need to re-enter Mosquitto credentials and Zigbee2MQTT config through the UI panels. Annoying but not catastrophic.


Making the Call

Pick HAOS if:

Pick Container if:


The Bottom Line

Add-Ons are Docker containers wearing a nicer suit and managed by someone else. That’s a good deal when you don’t want to be the someone else. It’s a frustrating limitation when you already are.

HAOS is the right default for a dedicated HA appliance. You get backups that actually work, a curated ecosystem of Add-Ons, and an OS that keeps itself updated. For a Pi in the closet running nothing but home automation, it’s the correct answer 90% of the time.

Container is the right choice when HA is one service in a larger homelab stack. The Add-On store is gone, but you get everything Compose offers: version pinning, custom configs, shared networking with your other services, and backup strategies that fit your existing workflow.

The only wrong answer is picking an install method without knowing what you gave up, then spending a Sunday afternoon wondering why the Add-On store tab doesn’t exist.

Your docker-compose.yml is waiting.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
FRR vs BIRD
Next Post
Local Vision LLMs Worth Running in 2026

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts