Guide de déploiement¶
Ce guide couvre le déploiement, la mise à jour, la sauvegarde et le dépannage de Sowel en production.
Déploiement initial¶
Sowel est livré sous forme d'image Docker à ghcr.io/mchacher/sowel:latest. Un déploiement de production se compose de deux conteneurs (sowel + sowel-influxdb) orchestrés par docker compose.
Prérequis¶
- Hôte Linux ou macOS avec Docker Engine 20.10+ et
docker composev2 (images multi-arch :linux/amd64etlinux/arm64— Raspberry Pi 4/5 supportés en natif) - Au moins 2 Go de RAM, 10 Go de disque (les données InfluxDB grossissent avec le temps)
- Accès réseau à
ghcr.iopour les pulls d'image etapi.github.compour les vérifications de version
Option A — Installation en une commande (recommandée)¶
Ce que ça fait :
- Vérifie que Docker et Docker Compose v2 sont installés et joignables
- Crée
~/sowel/(override avecSOWEL_DIR=/opt/sowel) - Télécharge le
docker-compose.ymlde référence - Détecte automatiquement le fuseau horaire de l'hôte et patche le compose (plus de logs en UTC)
- Tire les images, démarre la stack, attend que
/api/v1/healthréponde - Affiche l'URL et les commandes utiles (logs / update / stop)
Override du port d'hôte : SOWEL_PORT=8080 curl -fsSL ... | sh.
Si une installation existe déjà à $SOWEL_DIR, le script refuse d'écraser — choisissez un autre dossier ou supprimez l'existant d'abord.
Option B — Déploiement manuel¶
Pour un contrôle complet sur chaque étape :
# 1. Pick a deployment directory (convention: /opt/sowel)
sudo mkdir -p /opt/sowel
sudo chown $USER:$USER /opt/sowel
cd /opt/sowel
# 2. Download the reference docker-compose.yml
curl -O https://raw.githubusercontent.com/mchacher/sowel/main/docker-compose.yml
# 3. Optional: set your timezone (recommended — fixes calendar scheduling,
# HP/HC tariff classification, sunrise/sunset display)
# Edit docker-compose.yml and uncomment / add:
# - TZ=Europe/Paris
# 4. Launch
docker compose up -d
# 5. Check containers are up
docker compose ps
# 6. Open the UI and create the first admin
open http://<host>:3000
Au premier boot, Sowel :
- Crée sa base SQLite à
/app/data/sowel.db(sur le volumesowel-data) - Génère un secret JWT persistant (
data/.jwt-secret) et un token admin InfluxDB - Attend que vous créiez le premier admin via l'écran de setup de l'UI
Volumes¶
| Volume | Mount | Contenu |
|---|---|---|
sowel-data |
/app/data |
Base SQLite, logs, secrets, backups, fichiers de données |
sowel-plugins |
/app/plugins |
Fichiers de plugins installés (dist/, manifest.json, etc.) |
influxdb-data |
/var/lib/influxdb2 |
Stockage de séries temporelles |
Ce sont des volumes Docker nommés, persistants à travers les recréations de conteneurs. Ce sont eux qui font fonctionner l'auto-update et le backup/restore : les données stateful survivent.
Auto-update activé par défaut (depuis v1.15.3)¶
Le docker-compose.yml officiel monte /var/run/docker.sock dans le conteneur Sowel afin que le bouton "Mettre à jour maintenant" de l'UI Admin fonctionne dès la première installation.
Compromis sécurité : monter le socket Docker donne au conteneur le contrôle effectif du démon Docker hôte. Une RCE réussie contre Sowel (par ex. via une dépendance compromise) escalade en root sur l'hôte. Pour une installation domestique mono-utilisateur derrière un réseau de confiance ou un tunnel Cloudflare + auth admin, ce trade-off est généralement acceptable, mais reste un choix conscient.
Pour désactiver (déploiement hardening ou multi-tenant), retirer la ligne /var/run/docker.sock:/var/run/docker.sock du docker-compose.yml et relancer docker compose up -d. Le bouton "Mettre à jour" sera désactivé et POST /api/v1/system/update renverra 503 ; la mise à jour manuelle reste possible :
Ce défaut a été inversé en v1.15.3 (par rapport à la décision v1.7.0 / spec 105) après retour terrain : la quasi-totalité des installs tombait sur le message "update unavailable" sans deviner qu'il fallait copier un fichier override.
Exploitation¶
Vérifier le statut¶
docker compose ps
docker logs -f sowel # live logs from stdout
docker logs --tail 100 sowel # last 100 lines
Ou via l'API. En anonyme, seule la vivacité est retournée ; passez un token pour obtenir en plus les statuts d'intégration, le nombre de devices et la version du moteur :
curl -s http://localhost:3000/api/v1/health | jq
curl -s -H "Authorization: Bearer $SOWEL_TOKEN" http://localhost:3000/api/v1/health | jq
Redémarrer¶
Le ring buffer est en mémoire, le redémarrage le vide. Le log fichier (data/logs/sowel.<yyyy-MM-dd>.N.log) survit.
Stop / start¶
Reconstruire le conteneur (sans changement d'image)¶
Mises à jour¶
Sowel prend en charge deux chemins : auto-update depuis l'UI (facile) et mise à jour manuelle via compose (fallback).
Chemin 1 : auto-update depuis l'UI (préféré)¶
- Connectez-vous en admin
- Ouvrez les réglages / le badge de version. Si une mise à jour est disponible, un badge affiche "vX.Y.Z"
- Cliquez sur le badge → confirmez dans la modale
- Sowel crée un backup automatique, puis spawne un container helper qui fait le swap
- L'UI affiche un overlay "Update in progress"
- Après environ 30 à 90 secondes, la page se recharge sur la nouvelle version
Prérequis :
- Tourner sous
docker compose(Sowel le détecte via les labels du conteneur) /var/run/docker.sockmonté dans le conteneur soweldocker-compose.ymldans un répertoire bind-monté ou accessible sur l'hôte
Si un prérequis manque, le bouton Update est désactivé avec un tooltip qui explique quoi faire.
Chemin 2 : mise à jour manuelle via docker compose (fallback)¶
cd /opt/sowel
docker compose pull sowel # fetch the latest image from ghcr.io
docker compose up -d sowel # recreate the container
Sowel redémarre, les migrations s'exécutent automatiquement, les plugins sont auto-téléchargés s'ils sont manquants (spec 058), et l'UI reprend.
À utiliser quand :
- L'UI d'auto-update est désactivée (pas de socket Docker, déploiement non-compose)
- Mise à jour à travers une version qui contient elle-même un bug d'auto-update (par ex. depuis v1.0.6, qui avait la race condition corrigée en v1.0.7)
- Vous voulez épingler une version spécifique. Modifiez
docker-compose.ymlenghcr.io/mchacher/sowel:1.0.7avant lepull
Backup et restauration¶
Les backups capturent SQLite, les données InfluxDB et tous les fichiers data/* dynamiques dans un seul ZIP.
Backup manuel (export)¶
Depuis l'UI : Admin → Backup → "Télécharger un backup". Le navigateur télécharge un fichier sowel-backup-<date>.zip.
Depuis l'API :
TOKEN=$(curl -s http://<host>:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
--data-raw '{"username":"admin","password":"<pwd>"}' | jq -r .accessToken)
curl -s http://<host>:3000/api/v1/backup \
-H "Authorization: Bearer $TOKEN" \
-o sowel-backup.zip
Backups pré-update automatiques (locaux)¶
Avant chaque auto-update, Sowel crée un backup dans data/backups/sowel-backup-pre-v<version>-<timestamp>.zip. Les trois plus récents sont conservés ; les plus anciens sont rotés.
Lister les backups locaux :
- Depuis l'UI : Admin → Backup → section "Backups locaux"
- Via l'API :
GET /api/v1/backup/local
Restaurer un backup local :
- Depuis l'UI : cliquez sur "Restaurer" à côté du backup dans la liste
- Via l'API :
POST /api/v1/backup/restore-local { "filename": "sowel-backup-pre-v1.0.7-2026-04-11T08-28-45.zip" }
Restauration manuelle (import)¶
Depuis l'UI : Admin → Backup → "Charger un backup".
Depuis l'API :
curl -s -X POST http://<host>:3000/api/v1/backup \
-H "Authorization: Bearer $TOKEN" \
-F "file=@sowel-backup.zip"
Après restauration, Sowel renvoie { restartRequired: true }. Vous devez redémarrer le conteneur pour que l'état restauré prenne pleinement effet :
Restaurer un backup d'un autre déploiement (issue #401)¶
L'identifiant d'instance stocké dans la table settings voyage dans les backups. Quand une base restaurée porte l'identifiant d'un autre déploiement (backup de prod ouvert sur une machine de dev, migration vers un nouveau matériel), le moteur démarre inerte : intégrations sortantes, recettes, publishers et notifications restent désactivés, et un bandeau rouge s'affiche dans l'UI. Un admin confirme la reprise depuis ce bandeau (le moteur redémarre alors armé), ou vous pouvez pré-confirmer avec SOWEL_TAKEOVER=1 dans l'environnement. Cela empêche une copie des données de production de se connecter vers l'extérieur et de perturber le déploiement d'origine (collisions de clientId MQTT, courses sur les refresh tokens OAuth).
L'autre moitié de la comparaison est le fichier marqueur .instance-id situé à côté de la base, qui décrit le déploiement en cours d'exécution. Ce fichier n'est délibérément jamais embarqué dans un backup ni réécrit par une restauration : une instance conserve donc sa propre identité quelle que soit l'archive qu'on lui donne. Restaurer une archive de production sur une seconde machine déclenche donc bien le bandeau, et c'est précisément l'objectif : la restauration est la façon dont des données de production arrivent normalement là où elles n'ont rien à faire. Les versions antérieures embarquaient le marqueur dans l'archive, ce qui faisait venir les deux moitiés de la comparaison du même déploiement et empêchait silencieusement le garde-fou de se déclencher lors d'une restauration (issue #790).
Une instance démarrée avec SOWEL_SHADOW_MODE=1 est par construction une copie volontaire des données de production : elle a donc toujours une reprise en attente, et le bandeau y est supprimé. Les gates du mode shadow la maintiennent déjà inerte, et confirmer graverait l'identité d'origine dans le marqueur du shadow, supprimant la seconde ligne de défense pour le jour où la variable d'environnement sera oubliée.
Contenu de l'archive¶
Voir la section "Backup et restauration" dans architecture.md pour le format complet.
Accès aux logs¶
Trois sources¶
| Source | Rétention | Cas d'usage |
|---|---|---|
| Ring buffer (mémoire) | Perdu au redémarrage | Tail live via UI Admin → Logs |
| stdout Docker | Plafonné à 3 × 10 Mo par conteneur | docker logs sowel |
Fichiers pino-roll sur le volume sowel-data |
14 fichiers journaliers, survit à la recréation | Investigation post-incident, la seule source qui survit aux recréations d'auto-update |
Taille des logs Docker¶
docker-compose.yml plafonne le stdout de chaque conteneur à max-size: 10m /
max-file: 3. Sans ce plafond, le driver json-file de Docker est illimité :
un conteneur bavard grossit en silence jusqu'à saturer le disque de l'hôte, ce qui
met à terre tous les services de la machine, Sowel compris. Compose applique le
plafond à la création du conteneur : sur une install existante, il prendra
donc effet au prochain docker compose up -d (ou au prochain auto-update, qui
recrée le conteneur).
InfluxDB est le service le plus bavard : il journalise chaque requête HTTP au
niveau info par défaut (écritures, requêtes charts/history, exécutions des
tâches de downsampling), ce qui remplit son log. docker-compose.yml lui fixe
donc INFLUXD_LOG_LEVEL=warn, ce qui réduit le volume à la source tandis que le
plafond de taille ci-dessus reste le garde-fou. Passe à error pour un InfluxDB
encore plus silencieux.
Pour récupérer l'espace déjà perdu sur un hôte en marche, et repérer les autres coupables :
# Taille de chaque log de conteneur, le plus gros en dernier
for c in $(docker ps -aq); do
printf '%s\t%s\n' \
"$(sudo du -h "$(docker inspect --format '{{.LogPath}}' $c)" | cut -f1)" \
"$(docker inspect --format '{{.Name}}' $c)"
done | sort -h
# Vider un log en place, sans redémarrage
sudo truncate -s 0 "$(docker inspect --format '{{.LogPath}}' <conteneur>)"
Les conteneurs que Sowel ne gère pas (un Zigbee2MQTT installé à côté, par exemple)
gardent leurs propres réglages. Un défaut valable pour tout l'hôte se pose dans
/etc/docker/daemon.json, mais il ne s'applique qu'aux conteneurs créés après un
redémarrage du démon :
Accéder aux logs fichiers¶
# List files
docker exec sowel ls -la /app/data/logs/
# View today's log
docker exec sowel cat /app/data/logs/sowel.6.log
# Grep errors/warns in a time window
docker exec sowel sh -c 'cat /app/data/logs/sowel.6.log | grep -E "2026-04-11T07:" | grep -E "\"level\":\"(error|warn)\""'
Via le script helper¶
Depuis le repo (sur votre machine de dev) :
SOWEL_URL=http://<host>:3000 SOWEL_PASSWORD='<pwd>' \
python3 scripts/logs/fetch-logs.py "" error 100
# Filter by module
SOWEL_URL=http://<host>:3000 SOWEL_PASSWORD='<pwd>' \
python3 scripts/logs/fetch-logs.py recipe-manager debug 50
Cela interroge le ring buffer via l'API, donc seulement les logs depuis le dernier redémarrage.
Augmenter temporairement le niveau de log¶
curl -s -X PUT http://<host>:3000/api/v1/logs/level \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"level":"debug"}'
Cela n'affecte que le ring buffer (toujours à debug par défaut). Le transport fichier est au niveau racine défini via la variable d'env LOG_LEVEL.
Vérification de version¶
TOKEN=$(curl -s -X POST http://<host>:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
--data-raw '{"username":"admin","password":"<pwd>"}' | jq -r .accessToken)
curl -s http://<host>:3000/api/v1/system/version \
-H "Authorization: Bearer $TOKEN" | jq
Réponse attendue :
{
"current": "1.0.8",
"latest": "1.0.8",
"updateAvailable": false,
"releaseUrl": "https://github.com/mchacher/sowel/releases/tag/v1.0.8",
"dockerAvailable": true,
"composeManaged": true
}
Forcer un poll GitHub frais :
curl -s -X POST http://<host>:3000/api/v1/system/version/check \
-H "Authorization: Bearer $TOKEN" | jq
Dépannage¶
Le conteneur redémarre en boucle¶
Causes courantes :
- Erreur de migration de base de données, cherchez
migration faileddans les logs /var/run/docker.sockmanquant alors que l'auto-update est activé, devrait simplement warn, pas crasher- InfluxDB non joignable, vérifiez que
sowel-influxdbtourne
L'intégration ne se connecte pas¶
- Vérifiez le statut dans UI Admin → Intégrations ou via
GET /api/v1/integrations - Vérifiez les logs pour le module plugin spécifique :
plugin:<id> - Vérifiez que les réglages sont configurés sous
integration.<id>.*dans la tablesettings
L'auto-update échoue¶
Symptômes : vous cliquez sur "Update", l'overlay s'affiche, et la page ne recharge jamais.
Le moteur surveille le conteneur helper qu'il a lancé. Un helper qui réussit
recrée Sowel, donc ce processus meurt en premier ; si le helper se termine alors
que Sowel tourne encore, c'est que le swap n'a pas eu lieu. L'overlay est alors
remplacé par Échec de la mise à jour, citant la dernière sortie du helper —
le plus souvent un registre injoignable (dial tcp ...:443: i/o timeout). Seul
un administrateur voit ce texte : la couche WebSocket masque les chaînes libres
du serveur pour les autres rôles, qui ne voient donc que le résultat. Sowel
continue de servir la version précédente, et le backup pré-update qu'il a pris
est toujours dans data/backups/.
Un helper qui se bloque au lieu de sortir est traité lui aussi : au bout de quinze minutes le moteur l'abandonne, libère la mise à jour et le signale. L'abandonner ne le tue pas, donc s'il était seulement lent il peut encore mener le swap à son terme.
Récupération : corrigez la cause et recliquez sur Update. Il n'y a rien à
nettoyer, la tentative suivante supprime elle-même le helper resté en place. Le
seul cas qu'elle refuse est un helper de la tentative précédente encore en
cours : le supprimer couperait un docker compose up -d en deux, donc la mise
à jour s'arrête sur Helper "sowel-updater" from a previous attempt is still
running. Les deux helpers comptent, un updater bloque un restart et
réciproquement, puisqu'ils pilotent le même projet compose. Attendez-le, ou
supprimez-le à la main une fois ses logs lus (docker rm -f sowel-updater).
Passé la fenêtre de quinze minutes, la tentative suivante le supprime d'elle-même :
un helper orphelin laissé par un redémarrage de conteneur ne bloque pas les
mises à jour indéfiniment.
cd /opt/sowel
docker compose up -d # recrée le conteneur si le helper est mort en plein swap
docker compose pull && docker compose up -d # ou mise à jour à la main
Investigation :
docker logs sowel-updater: le conteneur helper est délibérément conservé après sa sortie (AutoRemove: false), précisément pour ça- Les logs propres de Sowel :
Update helper spawnedest la dernière ligne avant un swap réussi.Helper finished without restarting Sowelmarque un helper qui est sorti,Helper did not return within the watchdog windowun helper qui n'a jamais répondu - Si Sowel n'est jamais revenu du tout,
docker ps -aindique si son conteneur est en Exited
Base de données corrompue¶
SQLite est en mode WAL, sûr en cas d'arrêts brutaux dans la plupart des cas. En cas de corruption :
# Stop sowel
docker compose stop sowel
# Backup the corrupted DB
docker run --rm -v /opt/sowel_sowel-data:/data alpine cp /data/sowel.db /data/sowel.db.broken
# Restore from the most recent local backup
docker run --rm -v /opt/sowel_sowel-data:/data alpine ls /data/backups/
# Then use the restore flow (see above)
Bucket InfluxDB manquant après restauration¶
Si vous restaurez sur une machine fraîche, InfluxDB peut ne pas encore avoir de buckets. Le flux de restauration actuel (spec 059) appelle ensureBuckets() et ensureEnergyBuckets() avant d'écrire les données, donc cela devrait être automatique. Sinon, vérifiez les logs sowel-influxdb.
Logique horaire cassée (volets à la mauvaise heure, HP/HC erroné)¶
Le conteneur démarre par défaut en UTC. Définissez TZ=Europe/Paris (ou votre fuseau) dans docker-compose.yml puis redémarrez. Voir architecture.md § Gestion des fuseaux horaires et la spec 061 sur github.com/mchacher/sowel/tree/main/specs/061-timezone-from-home-location.
Exemple de déploiement de référence¶
Un déploiement domestique typique sur un seul hôte ressemble à ceci :
- Hôte : n'importe quelle VM Linux ou SBC (x86_64 ou ARM64, 4+ Go RAM recommandés)
- Chemin :
/opt/sowel/ - Accès : LAN
http://<votre-hote>:3000 - Conteneurs :
sowel+sowel-influxdb - Fuseau :
TZ=<votre-fuseau>explicitement défini dans le compose (workaround en attendant la spec 061) - Version actuelle :
docker logs sowel | grep "Sowel engine started" - Backups : locaux dans
data/backups/(auto), plus des téléchargements manuels conservés hors de l'hôte - MQTT :
mosquittoexterne tournant sur le même hôte (pas dans le compose), utilisé par les pluginszigbee2mqttetlora2mqtt - Zigbee2MQTT : daemon externe sur le même hôte, pas géré par Sowel lui-même. Une instance par coordinateur Zigbee — plusieurs instances partagent le broker, chacune avec son base topic et son répertoire de données, et le plugin
zigbee2mqttles dessert toutes. Voir Plusieurs coordinateurs Zigbee.
Le graphe de connectivité :
Internet
|
Cloudflare Tunnel (optionnel, voir le guide d'accès distant)
|
votre-hote (VM Linux)
+-- docker: sowel (port 3000)
+-- docker: sowel-influxdb
+-- docker: mosquitto (MQTT broker, 1883)
+-- systemd: zigbee2mqtt (lit le coordinateur Zigbee USB)
+-- systemd: lora2mqtt (lit le dongle LoRa USB)
+-- systemd: cloudflared (tunnel)