Tutorials 13 min read

Install Home Assistant with Docker in 2026: Step-by-Step Guide

Install Home Assistant Container with Docker Compose on 64-bit Linux: the official image, a secured Mosquitto broker, Zigbee2MQTT with your USB dongle, a TP-Link HS100 plug, and optional local HTTPS with Caddy.

See our picks
S By Selfhostr Team · no sponsored content
Install Home Assistant with Docker in 2026: Step-by-Step Guide
ⓘ ⓘ This article may contain affiliate links (no extra cost to you, it funds the site). As an Amazon Associate, Selfhostr earns from qualifying purchases. See the disclosure.
⏱️
~45 min
Duration
🎚️
Intermediate
Difficulty
💶
0 € (free software)
Cost
📦
Docker installed and functional.
Key Prerequisites

👍 What we like

  • ✓Officially supported Container method, using the multi-architecture ghcr.io/home-assistant/home-assistant:stable image.
  • ✓The whole stack (Home Assistant, MQTT, Zigbee) lives in one folder that is easy to back up and restore.
  • ✓Zigbee2MQTT and the TP-Link integration work locally, without the cloud.
  • ✓Controlled updates with docker compose pull, after reading the release notes.

👎 What to watch

  • ✕No add-on store as in Home Assistant OS: each extra service is a container you manage yourself.
  • ✕Requires an always-on 64-bit machine; 32-bit systems are no longer supported.
  • ✕Requires some knowledge of Docker, MQTT and Linux permissions.
  • ✕Recent HS100 firmware requires your TP-Link credentials for local access.

🏆 Our picks

Affiliate links · same price for you
Recommended
Raspberry Pi 4 Model B

Raspberry Pi 4 Model B

Voir sur Amazon

Amazon · price and availability on the listing

Convenient to Control via Home Assistant
📦

TP-Link HS100 Smart Plug Mini

Voir sur Amazon

Amazon · price and availability on the listing

For Advanced Storage Systems
Western Digital WD My Cloud EX2 Ultra NAS

Western Digital WD My Cloud EX2 Ultra NAS

Voir sur Amazon

Amazon · price and availability on the listing

See all our recommended gear in the shop.

📑 Contents ▾

There are several ways to install Home Assistant. This guide covers the Home Assistant Container method: Home Assistant run with Docker Compose on a Linux system you manage yourself. It is one of the two installation methods still officially supported, along with Home Assistant OS. The “Core” and “Supervised” methods are deprecated, and 32-bit systems (armv7, armhf, i386) have not been supported since release 2025.12. You will install, in a single Docker Compose stack:

  • Home Assistant, using the official ghcr.io/home-assistant/home-assistant:stable image;
  • Mosquitto, the MQTT broker;
  • Zigbee2MQTT, which drives your Zigbee USB dongle and passes your sensors to Home Assistant over MQTT;
  • optionally, Caddy, for HTTPS access on the local network.

The Container edition doesn’t have the add-on store that comes with Home Assistant OS. That’s why Mosquitto and Zigbee2MQTT run in their own containers here.

Prerequisites

  • A 64-bit Linux machine (amd64 or aarch64) that stays on all the time. A Raspberry Pi 4 Model B works if it runs a 64-bit system, such as Raspberry Pi OS 64-bit or Ubuntu Server 64-bit. For reliability, use a USB SSD rather than the microSD card.
  • Docker Engine 23.0 or newer with the Compose plugin (docker compose). The Home Assistant documentation says Docker Desktop is not supported.
  • A Zigbee USB dongle that Zigbee2MQTT supports. Note which chip it uses (Texas Instruments, Silicon Labs…): you will need it in step 7.
  • sudo access and a fixed IP address (or a DHCP reservation) for the server.
  • Optional: a TP-Link HS100 Smart Plug Mini. It is a Wi-Fi plug, not a Zigbee one. Home Assistant controls it locally through the “TP-Link Smart Home” integration.

About the Western Digital WD My Cloud EX2 Ultra: this NAS has a 32-bit ARM processor and its system does not provide standard Docker. It is therefore not suitable for hosting Home Assistant. It can, however, store copies of your backups on your local network.

Step-by-step installation

Step 1: Install Docker Engine and the Compose plugin

The commands below come from Docker’s Ubuntu documentation (22.04, 24.04 and 26.04 LTS are supported). On Raspberry Pi OS 64-bit or Debian, follow the “Debian” page of the Docker documentation instead. First uninstall any unofficial packages that are present (docker.io, docker-compose, docker-compose-v2, podman-docker).

sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Check:

sudo docker run --rm hello-world
sudo docker version --format '{{.Server.Version}}'
sudo docker compose version

hello-world should print its confirmation message. The server version should be 23.0 or higher, and docker compose version should respond without an error.

Step 2: Identify the Zigbee dongle

Plug in the dongle, then look up its stable path. The Zigbee2MQTT documentation recommends always using /dev/serial/by-id/…, because /dev/ttyUSB0 or /dev/ttyACM0 can change from one reboot to the next.

ls -l /dev/serial/by-id/

You should get a line like usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_…-if00-port0 -> ../../ttyUSB0. Copy the full name. If the folder doesn’t exist, the system isn’t detecting the dongle: check with sudo dmesg | tail -n 20 right after plugging it in.

Step 3: Create the directory tree and configure Mosquitto

The whole stack lives in one folder, which makes backups simpler.

mkdir -p ~/homeassistant/{homeassistant/config,mosquitto/config,mosquitto/data,mosquitto/log,zigbee2mqtt/data,caddy/data,caddy/config}
cd ~/homeassistant

tee mosquitto/config/mosquitto.conf <<'EOF'
listener 1883
allow_anonymous false
password_file /mosquitto/config/passwd
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
EOF

Mosquitto 2.x rejects anonymous clients as soon as a listener is declared, so you need to create accounts. Use one account per client: homeassistant and zigbee2mqtt. The -c option creates the file; use it only for the first account, or the file gets overwritten.

sudo docker run --rm -it -v "$PWD/mosquitto/config:/mosquitto/config" eclipse-mosquitto \
  mosquitto_passwd -c /mosquitto/config/passwd homeassistant
sudo docker run --rm -it -v "$PWD/mosquitto/config:/mosquitto/config" eclipse-mosquitto \
  mosquitto_passwd /mosquitto/config/passwd zigbee2mqtt
sudo docker run --rm eclipse-mosquitto id mosquitto

The last command prints the UID and GID of the image’s mosquitto user. Give that user the file and restrict its permissions (replace 1883:1883 if the command prints something else):

sudo chown 1883:1883 mosquitto/config/passwd
sudo chmod 0700 mosquitto/config/passwd

Check: sudo cat mosquitto/config/passwd shows two lines, homeassistant:$7$… and zigbee2mqtt:$7$…. The passwords in it are hashed.

Step 4: Write the compose.yaml file

Create ~/homeassistant/compose.yaml. The homeassistant service follows the official example: network_mode: host so network discovery works (including for the HS100 plug), and privileged: true. The dongle is passed only to Zigbee2MQTT. Replace REMPLACEZ-PAR-VOTRE-DONGLE with the name you noted in step 2.

services:
  homeassistant:
    container_name: homeassistant
    image: ghcr.io/home-assistant/home-assistant:stable
    volumes:
      - ./homeassistant/config:/config
      - /etc/localtime:/etc/localtime:ro
      - /run/dbus:/run/dbus:ro
    restart: unless-stopped
    stop_grace_period: 60s
    privileged: true
    network_mode: host
    environment:
      TZ: Europe/Paris
    depends_on:
      - mosquitto

  mosquitto:
    container_name: mosquitto
    image: eclipse-mosquitto
    restart: unless-stopped
    ports:
      - "127.0.0.1:1883:1883"
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log

  zigbee2mqtt:
    container_name: zigbee2mqtt
    image: ghcr.io/koenkk/zigbee2mqtt:latest
    restart: unless-stopped
    depends_on:
      - mosquitto
    ports:
      - "8080:8080"
    volumes:
      - ./zigbee2mqtt/data:/app/data
      - /run/udev:/run/udev:ro
    environment:
      TZ: Europe/Paris
    devices:
      - /dev/serial/by-id/REMPLACEZ-PAR-VOTRE-DONGLE:/dev/ttyACM0

  caddy:
    container_name: caddy
    image: caddy:2
    restart: unless-stopped
    network_mode: host
    profiles: ["proxy"]
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy/data:/data
      - ./caddy/config:/config

This file makes three choices:

  • Mosquitto listens only on 127.0.0.1 on the host. Home Assistant, in host mode, reaches it at 127.0.0.1:1883, and Zigbee2MQTT reaches it over Compose’s internal network under the name mosquitto.
  • The /run/dbus folder is only used by the Bluetooth integration. You can remove that line if you don’t use Bluetooth.
  • Caddy sits in the proxy profile, so it only starts when you ask for it (step 10).

Check:

sudo docker compose config --quiet && echo "compose.yaml valide"

If the command reports an error (indentation, unknown key…), it gives the offending line.

Step 5: Start the stack

sudo docker compose up -d
sudo docker compose ps

Check: all three services, homeassistant, mosquitto and zigbee2mqtt, are running (or Up). Then test the web server and the broker:

curl -sI http://127.0.0.1:8123 | head -n 1
sudo tail -n 5 mosquitto/log/mosquitto.log

The first command should return an HTTP/1.1 line with a 200 status or a redirect (3xx). The first start can take a minute or two: run the command again if it fails right away. The Mosquitto log should mention the listener opening on port 1883.

Step 6: First Home Assistant launch

From a computer on the local network, open http://SERVER_IP:8123. Create the owner account, then set your location, time zone and units. If you use UFW, allow port 8123 from your local network only (adjust the range):

sudo ufw allow from 192.168.1.0/24 to any port 8123 proto tcp

Check: once the wizard is done, the dashboard appears, and sudo ls homeassistant/config lists at least configuration.yaml and automations.yaml.

Step 7: Configure Zigbee2MQTT (onboarding)

On first start, Zigbee2MQTT runs an initial setup page on port 8080. Open http://SERVER_IP:8080 and fill in:

  • MQTT server: mqtt://mosquitto:1883, with the zigbee2mqtt user and its password;
  • Serial port: /dev/ttyACM0 (the path inside the container);
  • Adapter type: this depends on the chip, for example zstack for Texas Instruments or ember for Silicon Labs EFR32 (check the Zigbee2MQTT documentation for your dongle);
  • enable the Home Assistant integration (MQTT discovery).

Once you confirm the onboarding, Zigbee2MQTT starts. Check:

sudo cat zigbee2mqtt/data/configuration.yaml
sudo docker compose logs --tail 30 zigbee2mqtt

Among other things, the file should contain the equivalent of this:

mqtt:
  base_topic: zigbee2mqtt
  server: mqtt://mosquitto:1883
  user: zigbee2mqtt
serial:
  port: /dev/ttyACM0
homeassistant:
  enabled: true
frontend:
  enabled: true

The logs should contain neither Not authorized nor ECONNREFUSED, and should show that the connection to the MQTT server succeeded.

Step 8: Add the MQTT integration in Home Assistant

Since release 2022.3, the broker connection is no longer configured in YAML. Go to Settings > Devices & services > Add integration, search for MQTT, then enter:

  • Broker: 127.0.0.1
  • Port: 1883
  • Username: homeassistant, with its password

Check: in Settings > Devices & services > MQTT > Configure, under “Listen to a topic”, enter zigbee2mqtt/bridge/state and click “Start listening”. You should receive a message containing online. You can also run this from the server:

sudo docker compose exec mosquitto mosquitto_sub -h localhost -u homeassistant -P 'YOUR_PASSWORD' -t 'zigbee2mqtt/bridge/state' -C 1 -v

Step 9: Pair a Zigbee device, add the HS100 and create an automation

  1. In the Zigbee2MQTT interface (port 8080), click Permit join, then put your sensor into pairing mode (see its manual). It appears in the device list, and Home Assistant creates it automatically under Settings > Devices & services > MQTT.
  2. For the TP-Link HS100 plug: Settings > Devices & services. It often shows up under “Discovered”; if not, go to Add integration > TP-Link Smart Home and enter its IP address. Recent firmware asks for your TP-Link account credentials. If you get an authentication error, enable Third-Party Compatibility in the Kasa app (Settings > Third-Party Compatibility). After that, communication stays local, without the cloud.
  3. Create the automation in Settings > Automations & scenes > Create automation. In the ⋮ menu, choose “Edit in YAML” and paste the following, replacing the entity IDs with your own (you can find them in Settings > Devices & services > Entities):
alias: "Allumer la prise quand le couloir détecte un mouvement"
description: "Capteur Zigbee via Zigbee2MQTT -> prise TP-Link HS100"
triggers:
  - trigger: state
    entity_id: binary_sensor.capteur_couloir_occupancy
    to: "on"
actions:
  - action: switch.turn_on
    target:
      entity_id: switch.prise_salon
mode: single

Check: trigger the sensor. The plug should turn on, and the automation’s Trace (⋮ menu > Traces) shows the trigger and the action that ran.

Step 10 (optional): Local HTTPS with Caddy

The goal is HTTPS access on the local network (or through a VPN), without opening any port on your router. Since you can’t get a public certificate without exposing the server, this uses Caddy’s internal certificate authority (tls internal). The name ha.home.arpa is an example: add it to your router’s local DNS (or to the hosts file of your client machines) so it points to the server’s IP.

tee caddy/Caddyfile <<'EOF'
ha.home.arpa {
	tls internal
	reverse_proxy 127.0.0.1:8123
}
EOF
sudo docker compose --profile proxy up -d caddy
sudo docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile

Next, tell Home Assistant to trust the proxy. Otherwise, requests coming from the reverse proxy are blocked (the browser typically shows a 400 error). Since release 2026.8, this is set in Settings > System > Network, HTTP server section: enable Trust X-Forwarded-For and add 127.0.0.1 and ::1 to Trusted proxies. On an older release, add this block to homeassistant/config/configuration.yaml, then restart with sudo docker compose restart homeassistant:

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - 127.0.0.1
    - ::1

Check:

curl -kI --resolve ha.home.arpa:443:127.0.0.1 https://ha.home.arpa | head -n 1

The status should be 200 or a redirect, not 400. To stop browsers from showing a warning, import Caddy’s root certificate, caddy/data/caddy/pki/authorities/local/root.crt, on your machines.

Verification checks

cd ~/homeassistant
sudo docker compose ps
sudo docker compose logs --tail 50 homeassistant | grep -iE "error|exception" || echo "aucune erreur récente"
curl -sI http://127.0.0.1:8123 | head -n 1

Also check in the interface:

  • Settings > System > Repairs shows no blocking alert;
  • under Settings > Devices & services, the MQTT and TP-Link Smart Home integrations are active, with no errors.

Security and best practices

  • Never expose Home Assistant (8123), Zigbee2MQTT (8080), Mosquitto (1883) or SSH directly to the Internet. For remote access, use a VPN (WireGuard, for example). Don’t create any port forwarding on your router.
  • According to the Docker documentation, ports published by Docker (here, Zigbee2MQTT’s 8080) bypass UFW rules. If you want to restrict access to it, publish it on a specific IP ("192.168.1.10:8080:8080") or add rules to the DOCKER-USER chain.
  • Enable two-factor authentication in your Home Assistant Profile > Security, and keep a separate MQTT account for each client.
  • Updates: read the release notes (the “Backward-incompatible changes” section), back up, then run:
cd ~/homeassistant
sudo docker compose stop homeassistant zigbee2mqtt
sudo tar czf ~/sauvegarde-ha-$(date +%F).tar.gz homeassistant/config zigbee2mqtt/data mosquitto/config
sudo docker compose pull
sudo docker compose up -d
  • To stay in control of updates, you can replace stable with a specific version tag (see the tags published on Home Assistant’s GitHub package page). Then copy the archive to another device, for example a NAS share on your local network.

Troubleshooting common errors

  • docker: 'compose' is not a docker command: the plugin is missing. Install docker-compose-plugin (step 1). The old docker-compose (v1) binary is no longer maintained.
  • Zigbee2MQTT won’t start, with no such file or directory on the device: the /dev/serial/by-id/… path is wrong or the dongle is unplugged. Run ls -l /dev/serial/by-id/ again and fix compose.yaml.
  • Zigbee2MQTT logs Not authorized: the MQTT username or password is wrong, or the passwd file doesn’t exist. Check step 3 and the mosquitto/log/mosquitto.log log.
  • Zigbee2MQTT logs ECONNREFUSED: Mosquitto is stopped or the server setting is wrong. It must be mqtt://mosquitto:1883, not localhost, because localhost refers to the Zigbee2MQTT container itself.
  • Port 8123 doesn’t respond from the local network: the firewall is blocking it (see the UFW rule in step 6) or Home Assistant hasn’t finished starting (sudo docker compose logs -f homeassistant).
  • 400 error through Caddy: trusted proxies aren’t configured (step 10). The Home Assistant log reports a request from an unauthorized reverse proxy.
  • The HS100 isn’t discovered: it’s on another subnet or VLAN. Add it by its (fixed) IP, or enable Third-Party Compatibility in the Kasa app.

FAQ

Should I use ZHA or Zigbee2MQTT?

Both work, but never at the same time on the same dongle. ZHA is built into Home Assistant: you then pass the dongle to the homeassistant service (devices: block) and remove Zigbee2MQTT. Zigbee2MQTT runs separately, has its own interface and goes through MQTT, as in this guide.

Is the Raspberry Pi 4 Model B enough?

Yes, for home use, provided you run a 64-bit system: 32-bit images are no longer supported. For longevity, use a USB SSD rather than the microSD card.

Can I install Home Assistant on the WD My Cloud EX2 Ultra?

No. Home Assistant no longer supports its 32-bit ARM processor, and its system doesn’t offer standard Docker. Use it to store copies of your backup archives on the local network.

How do I update Home Assistant via Docker?

Run sudo docker compose pull and then sudo docker compose up -d, after reading the release notes and making a backup (see “Security and best practices”). Compose only recreates the containers whose image has changed.

Read also

Host this yourself

No hardware to manage? This kind of service runs on a managed VPS, ready in minutes. See PlanetHoster plans

Tags: home-assistantdockerzigbeehomelabautoheberge

Related

Tutorials

Configure Dockge to manage your Docker containers in 2026

Learn how to install and configure Dockge, a lightweight and self-hosted Docker Compose stack manager, perfect for homelabs and self-hosting.

Read
Tutorials

Install Frigate NVR on Raspberry Pi 5 in 2026: The Complete Guide

Install Frigate NVR on a Raspberry Pi 5: 64-bit Raspberry Pi OS, Docker from the official repository, the official image, a config.yml for an RTSP camera, secured port 8971 and SSD storage.

Read
Tutorials

Create a Raspberry Pi 5 NAS in 2026: Complete Guide

Guide for 2026 to transform a Raspberry Pi 5 into a high-performance NAS. Step-by-step guide on case, NVMe SSD, Docker, Samba setup and security for reliable self-hosting.

Read