This is how I have set up Home Assistant in a Docker, running on a Raspberry Pi, with Mosquitto, Zigbee2Mqtt and Traefik as reverse proxy.
I initially had all containers in the “proxy” Docker network, but that caused problems with Sonos, Chrome Cast. etc. so I put the Home Assistant container in network-mode host to fix the access problems.
This also allows accessing Home Assistant via HTTP, which is necessary to e.g. get text-to-speech i.e. streaming for media to work.
Traefik exposes HTTP only to redirect it to HTTPS.
In general I use a Let’s Encrypt wildcard certificate for my local network, but I had to create a special one for MQTTS, because Tasmota has some restrictions. The certificates are “deployed” via SMB shares to various Raspberry Pis.
All Docker containers are controlled via a single compose file.
Watchtower is used to notify about new docker versions.
compose.yml:
# the name is required, because when it is not set docker uses the directory name as default
# and since this compose file is linked to multiple directories that woulkd be a problem
name: home_assistant
services:
homeassistant:
image: homeassistant/home-assistant:2023.10.3
container_name: "homeassistant"
restart: always
stop_grace_period: 2m
volumes:
- "/opt/homeAssistant/cfg:/config"
- "/opt/letsencrypt:/letsencrypt:ro"
network_mode: "host"
# HASS was originally in the proxy network and could hence resolve mosquitto via its ervice name
# Since HASS is now in network mode "host" that resolution no longer works.
# To not have to change the configs inside HASS static IPs are assigned to the docker services
# and "extra_hosts" provides DNS name "mosquitto" to HASS.
extra_hosts:
- "mosquitto:172.21.0.2"
devices:
- "/dev/ttyUSB_RfxTrx:/dev/ttyUSB_RfxTrx"
environment:
- TZ=Europe/Vienna
labels:
- "com.centurylinklabs.watchtower.enable=true"
depends_on:
- mosquitto
zigbee2mqtt:
image: koenkk/zigbee2mqtt:1.33.0
container_name: zigbee2mqtt
restart: always
stop_grace_period: 2m
networks:
proxy:
# The static IP is not really necessary, but adedd for symmetry
ipv4_address: 172.21.0.4
volumes:
- /opt/zigbee2mqtt/data:/app/data
- /run/udev:/run/udev:ro
devices:
- /dev/ttyUSB_SonoffZigbee:/dev/ttyUSB0
environment:
TZ: Europe/Vienna
labels:
- "com.centurylinklabs.watchtower.enable=true"
depends_on:
- mosquitto
traefik:
image: traefik:2.10.4
container_name: traefik
command: --configFile=/etc/traefik/traefik.yml
restart: always
stop_grace_period: 2m
networks:
proxy:
# The static IP is not really necessary, but adedd for symmetry
ipv4_address: 172.21.0.3
ports:
- 80:80
- 443:443
- 8883:8883
volumes:
- /opt/traefik/cfg:/etc/traefik
- /opt/traefik/log:/var/log
- /opt/letsencrypt:/letsencrypt:ro
labels:
- "com.centurylinklabs.watchtower.enable=true"
mosquitto:
image: "eclipse-mosquitto:2.0.17"
container_name: "mosquitto"
restart: always
stop_grace_period: 2m
volumes:
- "/opt/mosquitto/cfg:/mosquitto/config"
- "/opt/mosquitto/data:/mosquitto/data"
- "/opt/mosquitto/log:/mosquitto/log"
- "/opt/letsencrypt:/letsencrypt:ro"
networks:
proxy:
# The static IP is needed for HASS - see comments in service "homeassistant" above.
ipv4_address: 172.21.0.2
environment:
TZ: Europe/Vienna
labels:
- "com.centurylinklabs.watchtower.enable=true"
# A network definition is necessary because homeassistant needs to define the IP or IP range
# of the reverse proxy in the configuration of the http integration.
# It is also necessary for the reverse proxy and to assign static IPs.
networks:
proxy:
ipam:
config:
- subnet: 172.21.0.0/16
traefik.yml:
global:
checkNewVersion: true
sendAnonymousUsage: true
entryPoints:
http:
address: ":80"
https:
address: ":443"
mqtt:
address: ":1883"
mqtts:
address: ":8883"
providers:
file:
# dynamic config reload does not work - docker?
filename: "/etc/traefik/dynamic.yml"
watch: true
api:
dashboard: true
# See https://doc.traefik.io/traefik/v2.10/observability/logs/
log:
filePath: "/var/log/traefik.log"
# level: "DEBUG"
# level: "INFO"
level: "WARN"
This is the dynamic Traefik config file:
dynamic.yaml:
tcp:
routers:
mosquitto-MQTT:
service: "mosquitto"
rule: "HostSNI(`*`)"
entryPoints: mqtts
tls: {}
services:
mosquitto:
loadBalancer:
servers:
- address: "mosquitto:1883"
http:
routers:
homeassistant-HTTP:
service: "homeassistant"
rule: "Host(`homeassistant.mydomain.com`)"
entryPoints: http
middlewares:
- "http2https"
homeassistant-HTTPS:
service: "homeassistant"
rule: "Host(`homeassistant.mydomain.com`)"
entryPoints: https
tls: {}
traefik-HTTP:
service: "api@internal"
rule: "Host(`traefik-raspi06.mydomain.com`)"
entryPoints: http
middlewares:
- "http2https"
traefik-HTTPS:
service: "api@internal"
rule: "Host(`traefik-raspi06.mydomain.com`)"
entryPoints: https
tls: {}
middlewares:
- traefikAuth
zigbee2mqtt-HTTP:
service: "zigbee2mqtt"
rule: "Host(`zigbee2mqtt.mydomain.com`)"
entryPoints: http
middlewares:
- "http2https"
zigbee2mqtt-HTTPS:
service: "zigbee2mqtt"
rule: "Host(`zigbee2mqtt.mydomain.com`)"
entryPoints: https
tls: {}
middlewares:
- zigbee2mqttAuth
middlewares:
http2https:
redirectScheme:
scheme: https
permanent: true
# See https://doc.traefik.io/traefik/v2.10/middlewares/http/basicauth/
traefikAuth:
basicAuth:
usersFile: "/etc/traefik/passwd.traefik"
zigbee2mqttAuth:
basicAuth:
usersFile: "/etc/traefik/passwd.zigbee2mqtt"
services:
homeassistant:
loadBalancer:
servers:
# Use IP instead of homeassistant.mydomain.com to be resilient against DNS problems
- url: "http://172.27.1.11:8123"
zigbee2mqtt:
loadBalancer:
servers:
- url: "http://zigbee2mqtt:8080"
tls:
# Define default certificate to be the mqtt certificate because traefik can not match certificates to addresses like "mosquitto:1883" - it would need a complete domain to match the certificate
# mqtt.mydomain.com is a special certificate for Tasmota (needs RSA - see https://dokuwiki.mydomain.com/computer/tasmota/start#mqtt)
stores:
default:
defaultCertificate:
certFile: /letsencrypt/live/mqtt.mydomain.com/fullchain.pem
keyFile: /letsencrypt/live/mqtt.mydomain.com/privkey.pem
certificates:
- certFile: /letsencrypt/live/mydomain.com/fullchain.pem
keyFile: /letsencrypt/live/mydomain.com/privkey.pem
This is just the relevant section of the Zigbee2Mqtt configuration.yaml:
permit_join: true
mqtt:
base_topic: zigbee2mqtt.raspi06
server: mqtt://mosquitto:1883
user: client
password: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
reject_unauthorized: false
keepalive: 60
version: 4
include_device_information: true
serial:
port: /dev/ttyUSB0
frontend:
port: 8080
url: https://zigbee2mqtt.mydomain.com
The relevant settings of mosquitto.conf:
listener 1883
socket_domain ipv4