Running Home Assistant for smart home automation
I resisted smart home stuff for a long time. Most of it requires a cloud account, phones home constantly, and breaks when the vendor shuts down. The Philips Hue bridge talks to Philips servers. The Tuya app talks to Tuya servers. Your light switch should not depend on an AWS region staying up.
Home Assistant changed my mind. It runs locally, supports thousands of devices, and does not phone home. I run it in Docker alongside the rest of my self-hosted stack, and it has become the central nervous system for everything in my house.
Why Home Assistant
Home Assistant is an open-source home automation platform written in Python. It runs a web interface on your local network where you can control devices, view sensor data, and build automations. The project has a huge community and integrates with over 2,000 device types out of the box.
The key selling point for me: everything stays local. Zigbee devices talk to a USB coordinator plugged into my server. MQTT messages never leave my network. Automations run on my hardware, not someone else's cloud. If my internet goes down, the lights still work.
The Zigbee stack
Before setting up Home Assistant, you need a way to talk to Zigbee devices. The setup has three parts:
- A Zigbee USB coordinator: I use the Sonoff Zigbee 3.0 USB Dongle Plus (ZBDongle-P), based on the TI CC2652P chip. It costs about $20 and works out of the box with Zigbee2MQTT.
- Zigbee2MQTT: A bridge that connects to the USB coordinator and translates Zigbee device messages into MQTT.
- Mosquitto: A lightweight MQTT broker that routes messages between Zigbee2MQTT and Home Assistant.
This might seem like a lot of moving parts for turning on a light. But the separation is what makes it flexible. Any device that speaks MQTT can participate in the system, including custom hardware like the LoRa weather station I built.
Docker Compose setup
Here is the full docker-compose.yml for all three services:
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: homeassistant
volumes:
- ./ha-config:/config
- /etc/localtime:/etc/localtime:ro
network_mode: host
restart: unless-stopped
mosquitto:
image: eclipse-mosquitto:2
container_name: mosquitto
ports:
- "1883:1883"
volumes:
- ./mosquitto/config:/mosquitto/config
- ./mosquitto/data:/mosquitto/data
- ./mosquitto/log:/mosquitto/log
restart: unless-stopped
zigbee2mqtt:
image: koenkk/zigbee2mqtt
container_name: zigbee2mqtt
volumes:
- ./zigbee2mqtt-data:/app/data
- /run/udev:/run/udev:ro
ports:
- "8080:8080"
devices:
- /dev/serial/by-id/usb-Silicon_Labs_Sonoff_Zigbee_3.0_USB_Dongle_Plus-if00-port0:/dev/ttyACM0
environment:
- TZ=Europe/Berlin
depends_on:
- mosquitto
restart: unless-stoppedA few things to note:
Home Assistant uses network_mode: host because it needs to discover devices on the local network via mDNS and SSDP. Running it on the bridge network breaks device discovery. The web UI is available at http://your-server-ip:8123.
The Zigbee USB dongle is passed through using the devices key. Always use the /dev/serial/by-id/ path, not /dev/ttyACM0. The by-id path is stable across reboots. The ttyACM number can change if you plug in another USB device.
Mosquitto needs a config file or it will reject connections. Mosquitto 2.0+ defaults to local-only mode. Create mosquitto/config/mosquitto.conf:
listener 1883
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.logThe allow_anonymous true is fine for a home network where the broker is not exposed to the internet. For anything else, set up password authentication.
Initial setup
Run docker compose up -d and open http://your-server-ip:8123 in a browser. Home Assistant walks you through creating an account, setting your location (for sunrise/sunset automations), and choosing which integrations to enable.
After the onboarding, add the MQTT integration: go to Settings, Devices & Services, Add Integration, search for MQTT. Point it at your Mosquitto broker (the IP of your Docker host, port 1883). Home Assistant will automatically discover devices that Zigbee2MQTT publishes.
Configuring Zigbee2MQTT
Create zigbee2mqtt-data/configuration.yaml:
mqtt:
base_topic: zigbee2mqtt
server: mqtt://172.17.0.1:1883
homeassistant: true
permit_join: false
serial:
port: /dev/ttyACM0
adapter: zstack
frontend:
port: 8080The server address 172.17.0.1 is the Docker bridge gateway, which reaches the Mosquitto container's published port. Setting homeassistant: true enables MQTT auto-discovery, so devices paired in Zigbee2MQTT automatically appear as entities in Home Assistant.
Set permit_join: true temporarily when pairing new devices. The Zigbee2MQTT web UI at port 8080 shows connected devices, signal strength, and lets you rename them.
Pairing a device
With permit_join enabled, put your Zigbee device in pairing mode (usually by holding a button for 5 seconds). Zigbee2MQTT picks it up within a few seconds and publishes it to MQTT. Home Assistant discovers it automatically.
I started with a couple of IKEA TRADFRI smart plugs and an Aqara temperature sensor. Both paired on the first try. The Aqara sensor reports temperature, humidity, pressure, and battery level. All of these show up as separate entities in Home Assistant.
Practical automations
Home Assistant automations have three parts: triggers, conditions, and actions. You can build them in the UI or write YAML directly in automations.yaml. I prefer YAML because I can version control it.
Turn on living room lights at sunset:
- alias: "Living room lights at sunset"
trigger:
- platform: sun
event: sunset
offset: "-00:30:00"
condition:
- condition: state
entity_id: person.jeremy
state: "home"
action:
- service: light.turn_on
target:
entity_id: light.living_room
data:
brightness_pct: 70This triggers 30 minutes before sunset, but only if I am home. The person.jeremy entity comes from the Home Assistant companion app on my phone, which reports location.
Temperature alert from the garden sensors:
In my LoRa weather station post, I mentioned wanting to pull sensor data into Home Assistant. The receiver already pushes data to InfluxDB over HTTP. To get it into Home Assistant, I publish the same data to MQTT. Home Assistant picks it up as a sensor entity through MQTT auto-discovery.
- alias: "Garden freeze warning"
trigger:
- platform: numeric_state
entity_id: sensor.garden_temperature
below: 2
action:
- service: notify.mobile_app_jeremy_phone
data:
title: "Freeze warning"
message: "Garden temperature is {{ states('sensor.garden_temperature') }}°C"Motion-activated hallway light:
- alias: "Hallway motion light"
trigger:
- platform: state
entity_id: binary_sensor.hallway_motion
to: "on"
condition:
- condition: sun
after: sunset
before: sunrise
action:
- service: light.turn_on
target:
entity_id: light.hallway
data:
brightness_pct: 40
- delay: "00:03:00"
- service: light.turn_off
target:
entity_id: light.hallwayThis only activates at night. During the day, the hallway gets enough natural light. The 3-minute delay before turning off is short enough to not waste power but long enough that you are usually past the hallway by then.
The maintenance reality
Home Assistant is powerful, but it is not set-and-forget.
Updates. The project releases monthly. Most updates are smooth. Occasionally one breaks an integration or changes how YAML automations work. I let updates sit for a week and check the release notes before applying them. The Docker setup makes rollbacks easy: just change the image tag.
YAML vs UI. You can build automations in the UI or in YAML files. The UI is fine for simple stuff, but complex automations with templates and conditions are easier to reason about in YAML. The downside: the UI and YAML automations live in different places, which gets confusing if you mix them.
Debugging. When an automation does not fire, check the automation trace in the UI. It shows exactly which trigger matched, which conditions passed or failed, and what actions ran. This saves a lot of guessing.
Zigbee mesh health. Zigbee devices form a mesh network. Battery-powered sensors connect through mains-powered devices (like smart plugs) to extend range. If you remove a smart plug, sensors behind it might lose connectivity. The Zigbee2MQTT network map visualizes the mesh topology, which helps when troubleshooting dropped devices.
Home Assistant is one of those projects where you spend a weekend setting it up, then spend the next six months adding "just one more automation." The Pi-hole I set up for network-wide ad blocking was the same way. These homelab services have a way of multiplying.
Sources
Related posts
Uptime Kuma told me everything was fine. It wasn't.
Green checkmarks are not observability. Here is what I learned building a real monitoring stack.
TrueNAS: reliable storage for your homelab
Why I use TrueNAS for network storage in my homelab, how to set it up, and the features that make it worth running over a simple file share.
Proxmox Backup Server: incremental backups done right
Why I use Proxmox Backup Server for my homelab backups, how incremental backups save massive amounts of storage, and how to set it up.
Enjoying the blog? Subscribe via RSS to get new posts in your reader.
Subscribe via RSS