Home Assistant on Raspberry Pi 4 with Docker and MQTT Auto-Discovery

Home Assistant is open-source home automation software that brings lights, plugs, sensors, cameras and thousands of other devices into one dashboard with automations that run locally, without depending on a cloud service. The Raspberry Pi 4 with 4 GB or 8 GB of RAM is one of the most common platforms for it.

This advanced tutorial runs Home Assistant and the Mosquitto MQTT broker as containers with Docker Compose, connects them, and then writes a Python script that publishes the Pi's own sensor data using MQTT discovery, so the entities appear in Home Assistant automatically without editing any YAML in Home Assistant.

Choosing an Installation Method

MethodWhat you getBest for
Home Assistant OSDedicated OS image with Supervisor and the add-on storeA Pi used only for Home Assistant
Home Assistant ContainerCore application in Docker; you manage other services yourselfA Pi that also runs other containers
Home Assistant Core in a venvManual Python installDevelopers only

Home Assistant OS is flashed with Raspberry Pi Imager like any other OS and is the simplest choice for a dedicated box. This article uses Container because it fits alongside the Docker setup from the previous article. The trade-off is that the add-on store is not available, so services like Mosquitto run as your own containers. Use the 64-bit Raspberry Pi OS; Home Assistant has been dropping support for 32-bit ARM systems.

The Docker Compose Stack

Create a folder ~/homeassistant containing the compose file below and a mosquitto/config/mosquitto.conf file.

~/homeassistant/docker-compose.yml - Home Assistant plus Mosquitto
services:
  homeassistant:
    image: ghcr.io/home-assistant/home-assistant:stable
    container_name: homeassistant
    volumes:
      - ./ha-config:/config              # all HA settings and the database
      - /etc/localtime:/etc/localtime:ro # use the Pi's timezone
      - /run/dbus:/run/dbus:ro           # needed for Bluetooth integrations
    network_mode: host                   # required for device discovery (mDNS, SSDP)
    privileged: true                     # access to USB radios and hardware
    restart: unless-stopped

  mosquitto:
    image: eclipse-mosquitto:2
    container_name: mosquitto
    ports:
      - "1883:1883"                      # MQTT
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log
    restart: unless-stopped
~/homeassistant/mosquitto/config/mosquitto.conf - password-protected broker
# Listen on all interfaces on the standard MQTT port
listener 1883
# Mosquitto 2.x refuses anonymous clients unless told otherwise; keep it that way
allow_anonymous false
password_file /mosquitto/config/passwd
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
cd ~/homeassistant
# Create the broker password file with a user called 'mqttuser'
docker run --rm -it -v $PWD/mosquitto/config:/mosquitto/config eclipse-mosquitto:2 \
    mosquitto_passwd -c /mosquitto/config/passwd mqttuser

docker compose up -d
docker compose logs -f homeassistant    # wait until startup finishes

Open http://pi4lab.local:8123, create the owner account, and set your location. Then go to Settings, Devices and services, Add integration, choose MQTT, and enter broker localhost (Home Assistant uses host networking), port 1883, and the mqttuser credentials.

How MQTT Discovery Works

Home Assistant listens on topics beginning with homeassistant/. When a device publishes a retained JSON message to homeassistant/<component>/<object_id>/config, Home Assistant creates the entity described in it: its name, unit, device class and the state topic where values will arrive. Because the config message is retained, the broker replays it whenever Home Assistant restarts.

TopicExample payloadPurpose
homeassistant/sensor/pi4lab_cpu_temp/configJSON entity descriptionCreates the entity
pi4lab/state{"cpu_temp": 52.1, "load1": 0.4}Carries the readings
pi4lab/availabilityonline or offlineMarks the device unavailable if it disconnects

Publishing Pi Sensors with Discovery

Install the client library with sudo apt install -y python3-paho-mqtt and run this script on the Pi, or on any other Pi on the network by changing BROKER.

pi_mqtt_sensors.py - publish CPU temperature and load with Home Assistant discovery
#!/usr/bin/env python3
# pi_mqtt_sensors.py - expose Raspberry Pi metrics to Home Assistant over MQTT
import json
import os
import socket
import time

import paho.mqtt.client as mqtt

BROKER, PORT = 'localhost', 1883
USER, PASSWORD = 'mqttuser', 'change-me'     # the mosquitto_passwd user
HOST = socket.gethostname()                   # e.g. pi4lab
STATE_TOPIC = f'{HOST}/state'
AVAIL_TOPIC = f'{HOST}/availability'

# Device block groups all entities under one device in Home Assistant
DEVICE = {'identifiers': [HOST], 'name': f'{HOST} Raspberry Pi 4',
          'manufacturer': 'Raspberry Pi Ltd', 'model': 'Raspberry Pi 4 Model B'}

SENSORS = {
    'cpu_temp': {'name': 'CPU temperature', 'unit_of_measurement': '\u00b0C',
                 'device_class': 'temperature', 'state_class': 'measurement'},
    'load1':    {'name': 'Load average 1 min', 'state_class': 'measurement'},
}

def read_values():
    with open('/sys/class/thermal/thermal_zone0/temp') as f:
        temp = int(f.read()) / 1000
    return {'cpu_temp': round(temp, 1), 'load1': round(os.getloadavg()[0], 2)}

# paho-mqtt 2.x needs a callback API version; 1.x does not have this attribute
try:
    client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
except AttributeError:
    client = mqtt.Client()
client.username_pw_set(USER, PASSWORD)
# Last will: broker publishes 'offline' if this script dies unexpectedly
client.will_set(AVAIL_TOPIC, 'offline', retain=True)
client.connect(BROKER, PORT, keepalive=60)
client.loop_start()                            # network loop in a background thread

# Publish one retained discovery config per sensor
for key, extra in SENSORS.items():
    config = {
        'unique_id': f'{HOST}_{key}',
        'state_topic': STATE_TOPIC,
        'value_template': '{{ value_json.' + key + ' }}',
        'availability_topic': AVAIL_TOPIC,
        'device': DEVICE,
        **extra,
    }
    client.publish(f'homeassistant/sensor/{HOST}_{key}/config',
                   json.dumps(config), retain=True)

client.publish(AVAIL_TOPIC, 'online', retain=True)

try:
    while True:
        values = read_values()
        client.publish(STATE_TOPIC, json.dumps(values))
        print(values)
        time.sleep(30)
except KeyboardInterrupt:
    client.publish(AVAIL_TOPIC, 'offline', retain=True)
finally:
    client.loop_stop()
    client.disconnect()

Within a few seconds a new device named after your Pi appears under the MQTT integration with two sensors. Add them to a dashboard card, or use the temperature in an automation, for example sending a notification when it exceeds 75 °C.

Key Points in the Script

  • unique_id lets Home Assistant track the entity across restarts and lets you rename it or change its icon from the user interface.
  • value_template picks one field out of the shared JSON state message, so a single publish updates every sensor at once.
  • will_set registers a last-will message with the broker. If the Pi loses power or the network drops, the broker publishes 'offline' on its behalf after the keepalive period expires.
  • retain=True on the config and availability messages means Home Assistant receives them even if it starts after the script.
  • state_class: measurement tells Home Assistant to keep long-term statistics, so the temperature appears in history graphs with hourly averages.

To keep the publisher running permanently, create a systemd service for it in the same way as the Gunicorn service in the web server article, with Restart=on-failure and After=network-online.target.

Troubleshooting

  • Not authorised in the script: the username or password does not match the file created by mosquitto_passwd. Recreate it and restart the mosquitto container.
  • No entities appear: confirm the config topics with mosquitto_sub -h localhost -u mqttuser -P <password> -t 'homeassistant/#' -v inside the container.
  • Entities show Unavailable: the availability topic says offline. Check that the script is running and that the retained 'online' message was sent.
  • Home Assistant slow or database errors: the recorder writes constantly. Run the stack from a USB SSD rather than a microSD card.
  • Zigbee USB stick not found: pass the device through, or rely on privileged: true, and use the stable path under /dev/serial/by-id/.

Summary

Home Assistant Container and Mosquitto run side by side in one compose file, with host networking for Home Assistant and a password-protected broker. Publishing retained discovery messages under homeassistant/sensor/.../config lets any Python script on any Pi create fully described entities automatically, with a last-will message marking them unavailable if the publisher stops. This is the foundation for bringing your own GPIO and sensor projects into a real home automation system.