HA<->MQTT
Mountain Lizard IconMountain Lizard Icon HA, MQTT, Docker
mountain-lizard
Instructions for configuring HomeBridge and MQTT in Docker on a Raspberry Pi

Requirements

You’ll need:

  1. A Raspberry Pi, I used a 5 8GB
  2. A micro SD card, or NVMe storage - I used a Pimoroni NVMe Base with a 256MB Raspberry Pi SSD. NVMe may be preferable for reliability.

Set up Raspberry OS

First, install the Pi with the standard 64-bit Raspberry Pi OS (at time of writing this was based on Debian 13 Trixie).

Either use the desktop edition and attach a screen, keyboard and mouse, or use the Lite version and set up SSH so you can log in to the Pi remotely (this is useful on the desktop as well).

In either case, set the host name to something recognisable, I used homeassistant. I set my user name to rebeam - this is used below, replace as necessary with your own user name.

Also make sure to set up your WiFi details in Imager if needed, although a wired ethernet connection is better for reliability if possible.

Log in to ssh to test (e.g. ssh username@homeassistant.local - you can omit the username if it matches the one on your PC).

To avoid having to type in the password every time, close the ssh session and then send your key with ssh-copy-id rebeam@homeassistant.local, substituting your user name on the pi for rebeam. If you’ve not yet generated a key, see this Github guide.

Network setup

For ease of use, set a static IP for the raspberry pi - the best way is usually to get your router to assign a specific IP based on mac address. This will also resolve issues if the homeassistant.local address doesn’t work for you, since you can use the static IP directly.

Set up Docker

We’ll install docker so we can run home assistant and MQTT in containers. I used these instructions and checked against the official docker install script docs and post-installation steps for Linux. One addition below is to restart the Pi after running sudo apt upgrade - when I attempted to install docker, it gave errors that were only resolved by a restart.

The summary is:

Update package index, upgrade installed packages:

sudo apt update
sudo apt upgrade

Restart:

sudo reboot -n

Run the docker install script:

curl -sSL https://get.docker.com | sh

Add your user to the docker group - note that if you are using a user name other than rebeam, you should change this below:

sudo usermod -aG docker rebeam

Log out:

logout

Log back in to get access to newly added group, and check you are in the docker group - this should print a list of groups including “docker”:

groups

Run “hello world” docker container to check it works:

docker run hello-world

Set up Docker containers for Home Assistant and Mosquitto

There’s a good guide on pimylifeup, but the home assistant docs specify additional options, so we’ve included these.

Create a directory named containers in your home directory:

cd ~
mkdir containers
cd containers

Create the following file as docker-compose.yml:

services:
  homeassistant:
    container_name: homeassistant
    image: "ghcr.io/home-assistant/home-assistant:stable"
    volumes:
      - ./volumes/homeassistant-config:/config
      - /etc/localtime:/etc/localtime
      - /run/dbus:/run/dbus:ro
    devices:
      - /dev/ttyUSB0:/dev/ttyUSB0
    restart: unless-stopped
    privileged: true
    network_mode: host
  mosquitto:
    image: eclipse-mosquitto
    restart: unless-stopped
    container_name: mosquitto
    volumes:
      - ./volumes/mosquitto:/mosquitto
    ports:
      - 1883:1883
      - 9001:9001

Note: We include the /dev/ttyUSB0 in case you have a USB Zigbee dongle and you want to use it from Home Assistant (via the “Zigbee Home Automation” integration built into Home Assistant). If you plan to use Zigbee2MQTT, omit the devices: and /dev/ttyUSB0 lines, and see the additional section covering Zigbee2MQTT.

Start the containers:

docker compose up -d

You can now create a config file in the mosquitto volume - note we are using sudo since the directories will have been created by docker running as root, so our normal user doesn’t have permission:

cd ~/containers/volumes/mosquitto
sudo mkdir config
sudo nano ./config/mosquitto.conf

Then paste the following minimal configuration - the following sets up anonymous access for anyone on your network, if you want you can configure a password here instead:

# following two lines required for > v2.0
allow_anonymous true
listener 1883
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

You can then restart the containers from the ~/containers dir:

cd ~/containers
docker compose restart

You can now also log in to Home Assistant at homeassistant.local:8123 and set up your admin password.

Test Mosquitto

We can use any mqtt client. MQTT Explorer looks like a good option.

For simplicity if you already have npm installed, you can use the mqtt.js command line client. Install it with:

npm install mqtt -g

You can now subscribe to messages from the mosquitto broker (server) - replace hostname.local with your server’s IP or appropriate local domain name:

mqtt sub -t 'hello' -h 'hostname.local' -v

Leave this running, and in another terminal we can publish a message:

mqtt pub -t 'hello' -h 'hostname.local' -m 'from MQTT.js'

This should print the message “hello from MQTT.js” in the first terminal window where we subscribed to the message.

Integrate MQTT with Home Assistant

We now need to let Home Assistant know about our MQTT broker so it can send/receive messages. We use the MQTT integration for this.

First, log in to Home Assistant, and navigate to “Settings” on the left pane, then “Devices & Services”.

Click ”+ ADD INTEGRATION” in the bottom right, then search for “MQTT” in the dialog that pops up. Select “MQTT”, then “MQTT” again.

This should display a dialog for MQTT details. Enter “localhost” for the “Broker”, leave “Port” as 1883, and leave “Username” and “Password” blank, then click “SUBMIT”. This should show a “Success” dialog, click “FINISH”.

To test this is working, navigate to “Settings” on the left pane, then “Devices & Services”. Find “MQTT” in the grid of devices and click it. Now click “Configure”, and you should see a page allowing you to publish and listen to messages. Start up and connect MQTT Explorer, or listen on the command line with mqtt sub -t 'home-assistant-test' -h 'houseassistant.local' -v. Then fill out the “Publish a packet” fields, with “Topic” as “houseassistant.local” and “Payload” as some test message, and click “PUBLISH”. You should see the message pop up in MQTT Explorer or on the command line where mqtt is running.

Migrating Home Assistant between Pis

If you are transferring from another container installation of Home Assistant, you can do this by just zipping up the volume directory on the original Pi:

cd ~/containers/volumes
sudo tar -zcvf homeassistant-config.tar.gz homeassistant-config

Then on the new Pi, stop the home assistant container:

cd ~/containers
docker compose down

Copy the homeassistant-config.tar.gz file from the old Pi to the new one, placing it in the ~/containers/volumes directory. Then rename the existing volume directory and replace with the contents of the tar:

cd ~/containers/volumes
sudo mv homeassistant-config homeassistant-config-original
sudo tar -zxvf homeassistant-config.tar.gz

Finally, restart the containers on the new Pi:

cd ~/containers
docker compose up -d

You should now have an identical copy of the old Home Assistant installation - if you’re using a Zigbee adaptor, make sure to move it to the new Pi, and restart the containers.

If you’re also using any other containers (Mosquitto, OpenThread border router, Matter server) then you should back up and restore the volume directories for those containers as well - if you want to transfer everything, just move over the enter volumes directory.

Backing up Home Assistant

First close down the docker container, then just zip up the volume directory on the Pi, and start up the containers again:

cd ~/containers
docker compose down

sudo tar -zcvf volumes.tar.gz volumes

docker compose up -d

The volumes directory contains a subdirectory for each of our containers, e.g. Home Assistant, Mosquitto, and OpenThread Border Router / Matter Server if installed.

Updating Home Assistant

Close down the docker container, then pull new images, and restart with a rebuild:

cd ~/containers
docker compose down

docker compose pull
docker compose up --force-recreate --build -d

# Optionally, remove unused images
docker image prune -f

This will also update the other services in the docker compose file.

Adding support for Matter/Thread

Newer home automation devices, particularly from Ikea, are now moving from Zigbee to using Matter and Thread. Thread essentially replaces Zigbee as a low power wireless mesh network, and Matter then covers how devices communicate over Thread and/or other networks (e.g. WiFi).

First you’ll need a USB Thread dongle, I used a ZBT-2, which supports either Zigbee or Thread (but only one at a time - you’ll need one dongle for each network if you want to support both).

If you’re running the Home Assistant Operating System things should be fairly plug and play, but if we want to keep our docker containers we will need to install the required parts ourselves.

The stack we need is as follows:

  1. The ZBT-2 adapter, configured with firmware to act as a Thread adapter.

  2. A thread border router, in this case the OpenThread border router. This will control the ZBT-2 adaptor, and route network traffic between our ethernet network and a new Thread wireless network.

  3. A Matter server, in this case the Python Matter Server. This is designed to provide HomeAssistant with an interface to the Matter network.

    Note: See the github page for details/updates - at time of writing, the Matter Server was being rewritten using matter.js, it sounds like a new version will replace the one linked above at some point.

  4. The relevant HomeAssistant integrations - one for the OpenThread border router, and one for Matter. (In addition there is a Thread integration, but this should be set up automatically by HA when needed).

  5. The HomeAssistant app running on a mobile device. HA requires use of a mobile app for pairing matter devices, since this requires either reading a QR code on the device, or using BlueTooth (and it’s easier to get a mobile device close to the Matter device to pair it).

Obviously we’ll also need at least one Matter device, I tried the process with an Ikea Bilresa switch.

These instructions were based on this HA post, but I also reviewed the for OpenThread border router and Python Matter Server to confirm/adjust the docker settings and produce a compose file.

The instructions below worked as of Home Assistant core version 2026.4.3, frontend 20260325.7, with docker images retrieved on 2026-04-20.

Now the actual process:

  1. Connect the ZBT2 to your Mac/PC, and go to the Open Home Foundation ZBT-2 toolbox page. You’ll need to use a browser that supports USB access, for example chrome.

  2. Update the ZBT2 firmware, selecting the “Thread” option. When complete it should show you are running “OpenThread” firmware, with version information.

  3. Connect the ZBT2 to your raspberry pi.

  4. Configure your raspberry pi to allow IP forwarding using the script from the openthread docker instructions. This needs to be done for the correct network adaptor for your main ethernet network. On the raspberry pi this was eth0 for me (use ifconfig to display a list of network adaptors). Update the value of “INFRA_IF_NAME” appropriately:

    INFRA_IF_NAME=eth0 curl -sSL https://raw.githubusercontent.com/openthread/ot-br-posix/refs/heads/main/etc/docker/border-router/setup-host | bash
    
  5. We now need to find the device id of the ZBT-2 dongle - run ls -lha /dev/serial/by-id/ to show all usb devices by id.

  6. Find the entry containing “ZBT-2” and copy the full text of the device name, something like “usb-Nabu_Casa_ZBT-2_9C139EAC8CE8-if00”. This will be used in your docker compose file.

  7. We will now add containers for the OpenThread border router (otbr) and Python Matter Server (matter-server) to our docker-compose.yaml file. Make sure to set the device id under “services -> otbr -> devices” to match the id from the previous step, it should now look like:

    services:
      homeassistant:
        container_name: homeassistant
        image: "ghcr.io/home-assistant/home-assistant:stable"
        volumes:
          - ./volumes/homeassistant-config:/config
          - /etc/localtime:/etc/localtime
          - /run/dbus:/run/dbus:ro
        devices:
          - /dev/serial/by-id/usb-Silicon_Labs_Sonoff_Zigbee_3.0_USB_Dongle_Plus_0001-if00-port0:/dev/ttyUSB0
        restart: unless-stopped
        privileged: true
        network_mode: host
      mosquitto:
        image: eclipse-mosquitto
        restart: unless-stopped
        container_name: mosquitto
        volumes:
          - ./volumes/mosquitto:/mosquitto
        ports:
          - 1883:1883
          - 9001:9001
    
      # See https://openthread.io/guides/border-router/build-docker for details
      otbr:
        container_name: otbr
        image: "openthread/border-router:latest"
        network_mode: host
        restart: unless-stopped
        volumes:
          - ./volumes/otbr:/data
        devices:
          # Set the id for the ZBT2 device to match the output of ls -lha /dev/serial/by-id/
          - /dev/serial/by-id/usb-Nabu_Casa_ZBT-2_9C139EAC8CE8-if00:/dev/ttyACM10
          - /dev/net/tun:/dev/net/tun
        cap_add:
          - NET_ADMIN
        environment:
          # Set to your timezone
          - TZ=Europe/London
          # Note this matches the device name for the ZBT, inside the container,
          # as shared by devices line above
          - OT_RCP_DEVICE=spinel+hdlc+uart:///dev/ttyACM10?uart-baudrate=460800
          # Change this to the main host network device, preferrably wired, this
          # is the "real" network the bridge will connect the Thread network to.
          - OT_INFRA_IF=eth0
          # The network interface name for the Thread network, should be fine to
          # leave as is
          - OT_THREAD_IF=wpan0
          - OT_LOG_LEVEL=7
          # You can use these to change port numbers if either default port
          # 8080 or 8081 are in use
          # - OT_REST_LISTEN_ADDR=0.0.0.0
          # - OT_REST_LISTEN_PORT=8981
          # - OT_WEB_LISTEN_ADDR=0.0.0.0
          # - OT_WEB_LISTEN_PORT=8980
    
      # See https://github.com/matter-js/python-matter-server/blob/main/docs/docker.md#running-using-docker-compose
      matter-server:
        image: ghcr.io/matter-js/python-matter-server:stable
        container_name: matter-server
        restart: unless-stopped
        # Required for mDNS to work correctly
        network_mode: host
        # security_opt:
          # Needed for Bluetooth via dbus
          # - apparmor:unconfined
        volumes:
          - ./volumes/matter:/data
          # Required for Bluetooth via D-Bus
          # - /run/dbus:/run/dbus:ro
    
    
  8. Remember to also change the OT_INFRA_IF value if you are not using eth0 for your main network, e.g. if you’re using wireless. Note there are some commented lines for changing port numbers if required, and for enabling bluetooth support in the matter server. I left bluetooth commented since I don’t have a use-case for it, and this seems like it might improve security by leaving apparmor enabled.

  9. You can now run docker compose up on the pi to check the new containers start. You should see log output without errors from otbr and matter-server, if not check that you have made the modifications listed above. You can then press “d” to detach from the containers (leaving them running).

  10. Make sure HA is up to date - Matter support is relatively new so it’s worth getting any fixes.

  11. Back in the HA web interface, go to “Settings -> Devices & services”, then click the ”+ Add integration” button in the bottom right. You can try installing the “Thread” integration at this stage, but it should display a notice saying it can only be installed once. Then add the “Open Thread Border Router” integration, when prompted use the URL “http://127.0.0.1:8081”. This points to the default port of the otbr server running in our container - if you modified this port number in the docker-compose.yaml file then change it here as well.

  12. Add another integration, “Matter”. Click the “Matter” line in the dialog, and accept the default websocket address, this will actually configure matter and connect it to the matter-server server running in our container.

  13. Now go to “Settings -> Matter -> Thread settings”. Find the OpenThread network (you may have others - for example I had an Apple Home one - ignore this), and click “Make preferred network”. Note that this step now seems to differ from the instructions on forum post, which install Thread integration separately and use settings inside it - looks like thread integration has been essentially pulled into matter integration. If you can’t find the settings in the exact same place, have a look around the Matter and/or Thread integration settings for a preferred network setting/button.

  14. Now on your mobile device running the Home Assistant app, go to “Settings -> Matter -> Add device”, and follow the instructions. I tested this with an Ikea Bilresa switch, where you need to scan the QR code on the back of the device itself. For me it took a few tries to pair but that might have been because I hadn’t set the preferred network first for the first try.

After pairing the switch it seemed pretty reliable for me, with low latency responding to presses. Using the “ping” feature from the device screen gave a rapid response.

Zigbee2MQTT

Zigbee2MQTT (Z2M) is an alternative to using the “Zigbee Home Automation” integration built into Home Assistant. Either approach works, but Zigbee2MQTT may have better support for some devices, and can also be used independently of Home Assistant.

We’ll run Z2M from another docker container, following the Z2M docker installation guide. I used a ZBT-2 adaptor, so the first step was to find the adaptor port following the Z2M adaptor docs. Plug int he adaptor and then run ls -l /dev/serial/by-id:

$ ls /dev/serial/by-id
usb-Nabu_Casa_ZBT-2_9C139EAC8CE8-if00

This should show the id of your adaptor. If you have more than one id shown, make sure only the adaptor is plugged in, if you see nothing then check you have a serial adaptor.

We can now add a new service to our ~/containers/docker-compose.yml file. The following file shows just the Z2M service alone, if you already have a docker-compose.yml then copy and paste everything starting from the zigbee2mqtt: line on the end of your existing file.

services:
  zigbee2mqtt:
    container_name: zigbee2mqtt
    image: ghcr.io/koenkk/zigbee2mqtt
    restart: unless-stopped
    volumes:
      - ./volumes/zigbee2mqtt:/app/data
      - /run/udev:/run/udev:ro
    ports:
      # Frontend port
      - 8080:8080
    environment:
      - TZ=Europe/London
    devices:
      # Make sure this matched your adapter location
      - /dev/serial/by-id/usb-Nabu_Casa_ZBT-2_9C139EAC8CE8-if00:/dev/ttyACM0

Replace the location after TZ= with your timezone identifier. Replace the id between /dev/serial/by-id/ and :/dev/ttyACM0 with the id you found using ls earlier. This makes the device available at /dev/serial/by-id/{id} on the host Pi available to the container as /dev/ttyACM0.

Start up the container with docker compose up -d (if it’s already running stop it with docker compose down first).

You can now navigate to http://homeassistant.local:8080 to go through the onboarding process.

If using a ZBT-2 adaptor, it may not be detected fully - under “Found Devices” select the id you set up earlier in docker-compose.yml, then select the following options (based on this nabu casa support page and the z2m adapter page - check under ZBT-2):

  • Coordinator/Adapter Port/Path: should already be filled out as /dev/ttyACM0
  • Coordinator/Adapter Type/Stack/Driver: select ember
  • Coordinator/Adapter Baudrate: select 460800
  • Coordinator/Adapter Hardware Flow Control : tick the box

Also change the following settings:

  • MQTT Server: use mqtt://172.17.0.1 - this connects to the MQTT server on the same docker host (this is what the slightly obtuse “tip” on the docker installation guide is referring to).
  • Frontend enabled?: tick the box
  • Home Assistant enabled?: tick the box

Click the “Submit” button to continue. Note that this will attempt to redirect to localhost, you’ll actually need to go back to http://homeassistant.local:8080 after 30 seconds to see the web frontend.

Note that this will only work if the MQTT server is actually running and accessible, otherwise you’ll just get the onboarding page again.

You can then pair devices by clicking the “Permit join” button - this will then swap to displaying “Disable join” with a timer counting down. This is a little confusing, but you can only pair while the button is in this “Disable join” state. Then put the device into pairing mode.

You should now be able to integrate Z2M into Home Assistant - see Z2M docs - essentially you should disable the ZHA integration in Home Assistant, and enable MQTT integration - see the Integrate MQTT with Home Assistant section above.

Quadlet / Podman

You can also use the Quadlet feature of Podman to run the containers, after a little adaptation of the config files. This is particularly useful on Bazzite, where podman is preinstalled and is a recommended approach to running containerised services.

First, you can use podlet to convert the docker-compose.yml file given in the docker containers section. After installing podlet, you can run podlet compose in the same directory as docker-compose.yml, this will output the contents for two .container files, one for each service.

However we do need to make some changes for podman relative to docker:

  1. Volume mounts need to be absolute, so instead of .volumes we need to create a directory, e.g. under your home directory, and use the absolute path. In the example below we created the volumes dir at /var/home/rebeam/podman/volumes and inserted this path - change this to wherever you made the volumes dir.
  2. Podman won’t create missing volume directories, so under the volumes dir you made, create a homeassistant-config dir, and for mosquitto create mosquitto and then a nested mosquitto/config dir.
  3. When mounting directories on the host as volumes under SELinux, we need to ask podman to create proper labels to allow the container to access the contents. This means we need to add :z to the end of the volumes.
  4. Where image locations don’t start with a hostname, we need to add docker.io - docker assumes this, podman needs it to be explicit.

After converting the docker compose file and adapting for podman, we get a file called homeassistant.container:

[Container]
AddDevice=/dev/ttyUSB0:/dev/ttyUSB0
ContainerName=homeassistant
Image=ghcr.io/home-assistant/home-assistant:stable
Network=host
PodmanArgs=--privileged
Volume=/var/home/rebeam/podman/volumes/homeassistant-config:/config:z
Volume=/etc/localtime:/etc/localtime:ro
Volume=/run/dbus:/run/dbus:ro

[Service]
Restart=always

Note that you may want to remove the PodmanArgs=--privileged and Volume=/run/dbus:/run/dbus:ro if you don’t need access to bluetooth devices, and remove AddDevice=/dev/ttyUSB0:/dev/ttyUSB0 if you don’t need access to that USB device (e.g. for a zigbee adapter).

And mosquitto.container:

[Container]
ContainerName=mosquitto
Image=docker.io/eclipse-mosquitto
PublishPort=1883:1883
PublishPort=9001:9001
Volume=/var/home/rebeam/podman/volumes/mosquitto:/mosquitto:z

[Service]
Restart=always

Finally, add a config for mosquitto - this is the same we used with docker - put the following in mosquitto/config/mosquitto.conf inside your volumes dir:

# following two lines required for > v2.0
allow_anonymous true
listener 1883
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

The default location for container files to run with systemd is at ~/.config/containers/systemd/ - copy both the container files here (you may need to create the directory).

We can now start up the containers:

systemctl --user daemon-reload
systemctl --user start mosquitto
systemctl --user start homeassistant

You can check the status of a container by name, e.g. systemctl --user status mosquitto, or stop with e.g. systemctl --user stop mosquitto

References