Docker bare-metal : Script d'installation Home-Assistant + Zigbee2MQTT + Mosquitto

Attention : qualité alpha


Bonjour,

Ce message est destiné aux utilisateurs qui souhaitent installer Home Assistant sous Docker en bare-metal, c’est à dire sans passer par un virtualiseur :

  • Home Assistant,
  • Mosquitto MQTT (optionnel mais recommandé),
  • Zigbee2MQTT (optionnel mais recommandé).

Le script est compatible avec les distributions de type Debian Linux et le système Synology DSM 7.3. Mais cela devrait fonctionner avec quelques modification sous tout système Docker.

Pourquoi Docker ? HAOS repose sur Docker, donc il peut être intéressant de revenir aux sources et de configurer HA en bare-metal, directement en Docker. Le système astucieux de mise à jour de HAOS n’est ni plus ni moins que celui de Docker, sauf que les commandes Docker ne sont pas accessibles depuis HAOS.

Trois avantages à une installation Docker :

  • Vous n’aurez plus besoin de virtualiseur. Vous n’avez plus à choisir entre KVM, Promox ou le virtualiseur de DSM 7.3, puisque seul Docker suffit. L’architecture devient très simple : l’OS principal est une machine Debian + OpenMediaVault (support mdadam, unraid ou ZFS) ou un NAS Synology (ou tout NAS supportant Docker) ou tout système supportant Docker (on pourrait même imaginer un Mac ou Windows). Toutes les applications tournent sous Docker, y compris HA, Z2M et MQTT.
  • Vous aurez accès à toute la logithéque Docker. C’est une solution flexible, mais qui demande un peu plus de configuration que HAOS.
  • En maitrisant Docker, vous gagnez en compétences. C’est toujours utile de connaître un mécanisme qui est caché à l’utilisateur dans HAOS. Vous connaîtrez mieux le fonctionnement de HAOS et d’ailleurs vous n’en aurez plus besoin.

Le script génère un fichier docker-compose.yml adapté aux besoins de l’utilisateur. Sont pris en compte les clés USB Zigbee et les coordinateurs réseau.

Je signale que je me suis aidé d’une IA en partant de mes propres fichiers de configuration. J’utilise un coordinateur réseau, donc je n’ai pas pu encore tester ce script avec une clé USB Zigbee, mais je le ferai dans les prochains jours. Sous Synology, il est possible que l’accès à la clé USB requiert des droits supplémentaires. Je dois tester tout cela. J’aimerais également générer une clé Zigbee aléatoire. Donc il y a encore plein de modifications à venir.

Dans l’idéal, j’aimerais poser des questions à l’utilisateur : « Souhaitez-vous utiliser Wireguard ? », etc … et générer le fichier docker-compose.yml adapté. On pourrait même imaginer télécharger et exécuter un script bash qui créé tous le fichiers nécessaires.

C’est un code de qualité alpha (=un premier jet, en devenir) destiné à discuter de la meilleure façon d’installer HA sous Docker. N’hésitez pas à faire vos remarques.

Easy-HA — Stack Home Assistant / Mosquitto / Zigbee2MQTT

Prérequis

  • Docker + Docker Compose installés
  • Une clé USB Zigbee ou un coordinateur réseau Zigbee (optionnel)
  • Un serveur Debian (ex: Raspberry Pi) ou un NAS Synology (DSM 7.3)

Installation

1. Créer l’arborescence sur le serveur

sudo mkdir -p /home/docker/home-assistant
sudo chown -R $(whoami) /home/docker
cd /home/docker/home-assistant

Vérifier que le dossier appartient bien à ton utilisateur :

ls -la /home/docker/

Le résultat doit afficher ton nom d’utilisateur comme propriétaire :

drwxr-xr-x  2 alice alice  ...  home-assistant

Si ce n’est pas le cas :

sudo chown -R $(whoami) /home/docker/home-assistant

2. Créer les dossiers de configuration

mkdir -p mosquitto/config
mkdir -p mosquitto/data
mkdir -p mosquitto/log
mkdir -p zigbee2mqtt
mkdir -p homeassistant

3. Déposer les fichiers du projet

Copier les fichiers suivants dans /home/docker/home-assistant/ :

/home/docker/home-assistant/
├── docker-compose.template.yml
├── .env
├── easy-ha.sh
├── mosquitto/
│   └── config/
│       └── mosquitto.conf
└── zigbee2mqtt/
    └── configuration.yaml

Rendre le script exécutable :

chmod +x easy-ha.sh

4. Choisir le mode Zigbee

Éditer .env et renseigner ZIGBEE_MODE selon ton équipement :

Pas de Zigbee

ZIGBEE_MODE=none

Zigbee2MQTT et Mosquitto ne sont pas inclus dans le docker-compose.yml généré.

Clé USB Zigbee

ZIGBEE_MODE=usb

Identifier le chemin de la clé sur l’hôte :

ls /dev/serial/by-id/

Puis renseigner ZIGBEE_ADAPTER dans .env :

ZIGBEE_ADAPTER=/dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_xxxx-if00

Concentrateur réseau Zigbee

ZIGBEE_MODE=network

Un concentrateur réseau est un coordinateur Zigbee accessible via TCP — par exemple
un ESP32 flashé avec Zigbee2MQTT-Assistant, ou une clé USB déportée via ser2net.
Renseigner son adresse et son port dans .env :

ZIGBEE_HOST=192.168.1.100
ZIGBEE_PORT=6638

5. Lancer la stack

./easy-ha.sh up

Au premier lancement, le script :

  • vérifie que Docker et Docker Compose sont disponibles
  • génère un mot de passe MQTT aléatoire de 32 caractères
  • l’écrit dans .env
  • crée le fichier passwd Mosquitto (hash bcrypt)
  • met à jour zigbee2mqtt/configuration.yaml selon le mode Zigbee choisi
  • génère le fichier docker-compose.yml adapté au mode Zigbee
  • démarre les conteneurs

Une fois initialisée, la stack peut être gérée directement avec Docker Compose :

sudo docker compose up -d
sudo docker compose down
sudo docker compose logs -f

Accès aux interfaces

Service URL Variable dans .env
Home Assistant http://<hôte>:HA_PORT HA_PORT=8123
Zigbee2MQTT http://<hôte>:Z2M_PORT Z2M_PORT=8081
Mosquitto MQTT <hôte>:MQTT_PORT MQTT_PORT=1883

Note : Le port de Home Assistant (HA_PORT) est informatif uniquement.
En mode network_mode: host, Docker ne gère pas le mapping de port — c’est
Home Assistant lui-même qui écoute sur ce port. Pour le changer, il faut
modifier homeassistant/configuration.yaml après le premier démarrage :

http:
  server_port: 8123

Home Assistant crée son compte administrateur à la première connexion via un assistant web.


Commandes easy-ha.sh

easy-ha.sh n’est utile qu’au premier lancement et pour régénérer la configuration.
Pour le reste, utiliser directement Docker Compose.

Commande Effet
./easy-ha.sh up Initialise si besoin et démarre
./easy-ha.sh reset Régénère les secrets et docker-compose.yml

Commandes Docker Compose courantes

sudo docker compose up -d      # démarrer
sudo docker compose down       # arrêter
sudo docker compose logs -f    # suivre les logs
sudo docker compose restart    # redémarrer
sudo docker compose pull       # mettre à jour les images

Changer de mode Zigbee

Modifier ZIGBEE_MODE dans .env, puis régénérer :

./easy-ha.sh reset

fichier docker-compose-template.yml

# docker-compose.template.yml — fichier de référence
# Ne pas modifier ni utiliser directement.
# easy-ha.sh génère docker-compose.yml à partir de ce template lors de l'init.

services:

  homeassistant:
    image: ghcr.io/home-assistant/home-assistant:latest
    container_name: homeassistant
    restart: unless-stopped
    network_mode: host
    privileged: false
    volumes:
      - ./homeassistant:/config
      - /etc/localtime:/etc/localtime:ro
    environment:
      - TZ=${TZ}
    depends_on:
      - mosquitto

  mosquitto:
    image: eclipse-mosquitto:latest
    container_name: mosquitto
    restart: unless-stopped
    ports:
      - "${MQTT_PORT}:1883"
      - "${MQTT_WS_PORT}:9001"
    volumes:
      - ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
      - ./mosquitto/config/passwd:/mosquitto/config/passwd:ro
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log
    environment:
      - TZ=${TZ}

  # Bloc zigbee2mqtt — injecté par easy-ha.sh selon ZIGBEE_MODE :
  #
  # none    : bloc absent
  #
  # usb     :
  #   zigbee2mqtt:
  #     ports:
  #       - "${Z2M_PORT}:8080"
  #     devices:
  #       - /dev/serial/by-id/xxx:/dev/ttyUSB0
  #
  # network :
  #   zigbee2mqtt:
  #     ports:
  #       - "${Z2M_PORT}:8080"
  #     (pas de devices — port TCP dans configuration.yaml)

fichier .env

# ── Général ──────────────────────────────────────────────────────────────────

TZ=Europe/Paris

# ── Home Assistant ───────────────────────────────────────────────────────────

# Informatif uniquement — port contrôlé par HA en mode network_mode: host
HA_PORT=8123

# ── Mosquitto MQTT ───────────────────────────────────────────────────────────

MQTT_PORT=1883
MQTT_WS_PORT=9001

# Credentials — générés automatiquement par easy-ha.sh, ne pas modifier à la main
MQTT_USER=zigbee2mqtt
MQTT_PASSWORD=changeme

# ── Zigbee2MQTT ──────────────────────────────────────────────────────────────

Z2M_PORT=8081

# Mode Zigbee2MQTT : none | usb | network
ZIGBEE_MODE=none

# Mode USB — chemin de la clé Zigbee sur l'hôte
# Vérifier avec : ls /dev/serial/by-id/
ZIGBEE_ADAPTER=/dev/serial/by-id/usb-VOTRE_CLE_ZIGBEE-if00

# Mode réseau — coordinateur Zigbee via TCP (ex. ESP32)
ZIGBEE_HOST=192.168.1.100
ZIGBEE_PORT=6638

mosquitto.conf

persistence true
persistence_location /mosquitto/data/

log_dest file /mosquitto/log/mosquitto.log
log_dest stdout

listener 1883
protocol mqtt

listener 9001
protocol websockets

# Authentification obligatoire
allow_anonymous false
password_file /mosquitto/config/passwd

Fichier Zigbee2MQTT : configuration.yaml

homeassistant: true

mqtt:
  base_topic: zigbee2mqtt
  server: mqtt://localhost:1883
  user: zigbee2mqtt
  password: changeme             # remplacé automatiquement par easy-ha.sh

serial:
  port: /dev/ttyUSB0             # remplacé automatiquement par easy-ha.sh selon ZIGBEE_MODE

frontend:
  port: 8080

advanced:
  log_level: info

Fichier easy-ha.sh

#!/bin/sh
# easy-ha.sh — gestion de la stack Home Assistant / Mosquitto / Zigbee2MQTT
# Compatible POSIX sh : Debian (bash/dash) et Synology DSM 7 (ash/BusyBox)
# Usage : ./easy-ha.sh [up|down|logs|restart|reset]

set -eu

ENV_FILE=".env"
COMPOSE_FILE="docker-compose.yml"
PASSWD_FILE="mosquitto/config/passwd"
Z2M_CONFIG="zigbee2mqtt/configuration.yaml"

# ─── Détection de l'environnement ───────────────────────────────────────────

OS="linux"
if [ -f /etc/synoinfo.conf ]; then
  OS="synology"
fi

if [ "$OS" = "synology" ]; then
  DOCKER="docker"
else
  DOCKER="sudo docker"
fi

echo "[INFO] Environnement détecté : $OS"

# ─── Vérification de Docker ──────────────────────────────────────────────────

if ! command -v docker > /dev/null 2>&1; then
  echo "[ERREUR] Docker est introuvable sur cette machine."
  echo "         Debian  : https://docs.docker.com/engine/install/debian/"
  echo "         Synology: installer Container Manager depuis le Centre de paquets."
  exit 1
fi
echo "[OK] Docker trouvé : $(docker --version)"

if ! $DOCKER compose version > /dev/null 2>&1; then
  echo "[ERREUR] Le plugin 'docker compose' est introuvable."
  echo "         Il est inclus dans Docker Engine >= 20.10."
  exit 1
fi
echo "[OK] Docker Compose trouvé : $($DOCKER compose version)"

# ─── Lecture du .env ─────────────────────────────────────────────────────────

read_env() {
  ZIGBEE_MODE=$(grep "^ZIGBEE_MODE=" "$ENV_FILE" | cut -d= -f2-)
  ZIGBEE_ADAPTER=$(grep "^ZIGBEE_ADAPTER=" "$ENV_FILE" | cut -d= -f2-)
  ZIGBEE_HOST=$(grep "^ZIGBEE_HOST=" "$ENV_FILE" | cut -d= -f2-)
  ZIGBEE_PORT=$(grep "^ZIGBEE_PORT=" "$ENV_FILE" | cut -d= -f2-)
  MQTT_USER=$(grep "^MQTT_USER=" "$ENV_FILE" | cut -d= -f2-)
  MQTT_PASSWORD=$(grep "^MQTT_PASSWORD=" "$ENV_FILE" | cut -d= -f2-)
  HA_PORT=$(grep "^HA_PORT=" "$ENV_FILE" | cut -d= -f2-)
  MQTT_PORT=$(grep "^MQTT_PORT=" "$ENV_FILE" | cut -d= -f2-)
  MQTT_WS_PORT=$(grep "^MQTT_WS_PORT=" "$ENV_FILE" | cut -d= -f2-)
  Z2M_PORT=$(grep "^Z2M_PORT=" "$ENV_FILE" | cut -d= -f2-)
}

# ─── Génération de docker-compose.yml ────────────────────────────────────────

generate_compose() {
  echo "[INFO] Génération de $COMPOSE_FILE (mode : $ZIGBEE_MODE)..."

  # Bloc de base : Home Assistant + Mosquitto
  cat > "$COMPOSE_FILE" << YAML
# docker-compose.yml — généré automatiquement par easy-ha.sh
# Ne pas modifier à la main. Relancer ./easy-ha.sh reset pour régénérer.

services:

  homeassistant:
    image: ghcr.io/home-assistant/home-assistant:latest
    container_name: homeassistant
    restart: unless-stopped
    network_mode: host
    privileged: false
    volumes:
      - ./homeassistant:/config
      - /etc/localtime:/etc/localtime:ro
    environment:
      - TZ=\${TZ}
    depends_on:
      - mosquitto

  mosquitto:
    image: eclipse-mosquitto:latest
    container_name: mosquitto
    restart: unless-stopped
    ports:
      - "${MQTT_PORT}:1883"
      - "${MQTT_WS_PORT}:9001"
    volumes:
      - ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
      - ./mosquitto/config/passwd:/mosquitto/config/passwd:ro
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log
    environment:
      - TZ=\${TZ}
YAML

  # Bloc Zigbee2MQTT conditionnel
  case "$ZIGBEE_MODE" in
    usb)
      cat >> "$COMPOSE_FILE" << YAML

  zigbee2mqtt:
    image: koenkk/zigbee2mqtt:latest
    container_name: zigbee2mqtt
    restart: unless-stopped
    ports:
      - "${Z2M_PORT}:8080"
    volumes:
      - ./zigbee2mqtt:/app/data
      - /run/udev:/run/udev:ro
    environment:
      - TZ=\${TZ}
    devices:
      - ${ZIGBEE_ADAPTER}:/dev/ttyUSB0
    group_add:
      - dialout
    depends_on:
      - mosquitto
YAML
      ;;
    network)
      cat >> "$COMPOSE_FILE" << YAML

  zigbee2mqtt:
    image: koenkk/zigbee2mqtt:latest
    container_name: zigbee2mqtt
    restart: unless-stopped
    ports:
      - "${Z2M_PORT}:8080"
    volumes:
      - ./zigbee2mqtt:/app/data
    environment:
      - TZ=\${TZ}
    depends_on:
      - mosquitto
YAML
      ;;
    *)
      echo "[INFO] Mode none — Zigbee2MQTT non inclus dans $COMPOSE_FILE."
      ;;
  esac

  echo "[OK] $COMPOSE_FILE généré."
}

# ─── Initialisation des secrets ──────────────────────────────────────────────

do_init() {
  if [ ! -f "$ENV_FILE" ]; then
    echo "[ERREUR] Fichier $ENV_FILE introuvable."
    echo "         Lance ce script depuis le répertoire du projet."
    exit 1
  fi

  if [ ! -w "$ENV_FILE" ]; then
    echo "[ERREUR] Pas de permission d'écriture sur $ENV_FILE."
    echo "         Corriger avec : sudo chown -R $(whoami) /home/docker/home-assistant"
    exit 1
  fi

  read_env

  MQTT_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | dd bs=32 count=1 2>/dev/null)

  if [ -z "$MQTT_PASSWORD" ]; then
    echo "[ERREUR] Impossible de générer un mot de passe."
    exit 1
  fi

  echo ""
  echo "  ┌─────────────────────────────────────────────┐"
  echo "  │  Utilisateur MQTT : $MQTT_USER"
  echo "  │  Mot de passe     : $MQTT_PASSWORD"
  echo "  │                                             │"
  echo "  │  Note-le si tu en as besoin (client MQTT,   │"
  echo "  │  debug, autre service). Il est aussi        │"
  echo "  │  disponible dans le fichier .env.           │"
  echo "  └─────────────────────────────────────────────┘"
  echo ""

  TMP=$(mktemp)
  sed "s|^MQTT_PASSWORD=.*|MQTT_PASSWORD=${MQTT_PASSWORD}|" "$ENV_FILE" > "$TMP"
  cat "$TMP" > "$ENV_FILE"
  rm "$TMP"
  echo "[OK] $ENV_FILE mis à jour."

  mkdir -p "$(dirname "$PASSWD_FILE")"
  rm -f "$PASSWD_FILE"

  if command -v mosquitto_passwd > /dev/null 2>&1; then
    mosquitto_passwd -b -c "$PASSWD_FILE" "$MQTT_USER" "$MQTT_PASSWORD"
    echo "[OK] $PASSWD_FILE créé via mosquitto_passwd local."
  else
    echo "[INFO] mosquitto_passwd absent sur l'hôte, utilisation du conteneur..."
    $DOCKER run --rm \
      -v "$(pwd)/$(dirname "$PASSWD_FILE"):/mosquitto/config" \
      eclipse-mosquitto:latest \
      mosquitto_passwd -b -c /mosquitto/config/passwd "$MQTT_USER" "$MQTT_PASSWORD"
    echo "[OK] $PASSWD_FILE créé via le conteneur Mosquitto."
  fi

  do_zigbee_config
  generate_compose
  echo "[OK] Initialisation terminée."
}

# ─── Configuration Zigbee2MQTT selon le mode ─────────────────────────────────

do_zigbee_config() {
  if [ ! -f "$Z2M_CONFIG" ]; then
    echo "[AVERT] $Z2M_CONFIG introuvable — configuration Zigbee ignorée."
    return
  fi

  if [ ! -w "$Z2M_CONFIG" ]; then
    echo "[ERREUR] Pas de permission d'écriture sur $Z2M_CONFIG."
    echo "         Corriger avec : sudo chown -R $(whoami) /home/docker/home-assistant"
    exit 1
  fi

  TMP=$(mktemp)
  sed "s|^  password:.*|  password: ${MQTT_PASSWORD}|" "$Z2M_CONFIG" > "$TMP"
  cat "$TMP" > "$Z2M_CONFIG"
  rm "$TMP"

  case "$ZIGBEE_MODE" in
    usb)
      SERIAL_PORT="/dev/ttyUSB0"
      ;;
    network)
      SERIAL_PORT="tcp://${ZIGBEE_HOST}:${ZIGBEE_PORT}"
      ;;
    *)
      echo "[INFO] ZIGBEE_MODE=none — configuration série ignorée."
      return
      ;;
  esac

  TMP=$(mktemp)
  sed "s|^  port:.*|  port: ${SERIAL_PORT}|" "$Z2M_CONFIG" > "$TMP"
  cat "$TMP" > "$Z2M_CONFIG"
  rm "$TMP"
  echo "[OK] $Z2M_CONFIG mis à jour (mode : $ZIGBEE_MODE, port : $SERIAL_PORT)."
}

# ─── Commandes ───────────────────────────────────────────────────────────────

cmd_up() {
  if [ ! -f "$PASSWD_FILE" ] || [ ! -f "$COMPOSE_FILE" ]; then
    do_init
  else
    read_env
    echo "[INFO] Initialisation déjà effectuée."
    echo ""
    echo "  ┌─────────────────────────────────────────────┐"
    echo "  │  Utilisateur MQTT : $MQTT_USER"
    echo "  │  Mot de passe     : $MQTT_PASSWORD"
    echo "  │  (disponible dans le fichier .env)          │"
    echo "  └─────────────────────────────────────────────┘"
    echo ""
  fi
  $DOCKER compose up -d
}

cmd_reset() {
  echo "[AVERT] Ceci supprime les données Mosquitto, régénère les secrets et docker-compose.yml."
  printf "Confirmer ? (o/N) : "
  read -r CONFIRM
  case "$CONFIRM" in
    [oO])
      $DOCKER compose down
      rm -f "$PASSWD_FILE"
      rm -f "$COMPOSE_FILE"
      do_init
      $DOCKER compose up -d
      ;;
    *)
      echo "Abandon."
      ;;
  esac
}

cmd_help() {
  echo "Usage : ./easy-ha.sh [commande]"
  echo ""
  echo "Commandes disponibles :"
  echo "  up       Initialise si besoin et démarre la stack"
  echo "  reset    Régénère les secrets et docker-compose.yml, puis redémarre"
  echo ""
  echo "Modes Zigbee (ZIGBEE_MODE dans .env) :"
  echo "  none     Zigbee2MQTT désactivé (défaut)"
  echo "  usb      Clé USB Zigbee (ZIGBEE_ADAPTER)"
  echo "  network  Coordinateur réseau (ZIGBEE_HOST / ZIGBEE_PORT)"
  echo ""
  echo "Commandes Docker Compose courantes :"
  echo "  sudo docker compose down"
  echo "  sudo docker compose logs -f"
  echo "  sudo docker compose restart"
  echo "  sudo docker compose pull"
}

case "${1:-help}" in
  up)    cmd_up ;;
  reset) cmd_reset ;;
  *)     cmd_help ;;
esac

Cordialement, Kellogs