Déploiement avec Podman
Chatbotaurus utilise Podman, pas Docker, pour ses conteneurs. Ce guide
décrit les deux plans de déploiement, l'environnement de développement
(podman-compose) et la production (unités Quadlet).
À la fin de ce chapitre, vous aurez créé les réseaux et les volumes, démarré les deux plans en développement, et vous saurez comment la production est déployée, suivie et ramenée à l'image précédente.
Prérequis
- Podman et
podman-compose. Tapez bienpodman-composeavec un tiret :podman compose(avec un espace) n'est qu'un adaptateur qui cherche un fournisseur externe. - Pour la production : un hôte Linux avec systemd, un compte
non privilégié dédié (noté
<compte-deploiement>ci-dessous) et les unités Quadlet decontainers/. - Les fichiers
podman-compose.ymletpodman-compose.vps2.yml, le dossiercontainers/et les scripts cités dans ce chapitre font partie des sources sous licence (voie d'accès : Contribuer). Lancez les commandespodman-composedepuis la racine de ces sources. - Aucun fichier
.envn'est nécessaire pour un premier essai en développement : les fichiers compose portent des valeurs par défaut de développement. Le stockage objet fait exception : il reste fermé tant que ses variablesSEAWEEDFS_S3_*ne sont pas renseignées (voir Problèmes connus, section « Le stockage objet S3 refuse toutes les requêtes »).
Deux plans, deux réseaux
| Plan | Projet et réseau | Fichier | Nom des services |
|---|---|---|---|
| Backend (notre plan) | chatbotaurus-vps1 | podman-compose.yml | forge-* |
| Client | mgaas-vps2 | podman-compose.vps2.yml | mcp-* |
Règle : un service nommé mcp-* est dans le plan client, tout autre nom
dans le plan backend. Tout nouveau serveur MCP est un service du fichier
podman-compose.vps2.yml, jamais un podman run isolé.
Les deux réseaux sont créés avant le premier lancement (les fichiers
compose les déclarent external) :
podman network create chatbotaurus-vps1
podman network create mgaas-vps2
Les volumes de données sont external eux aussi (bloc volumes: en fin de
chaque fichier compose) : compose ne les crée pas. Créez-les avant le premier
lancement. Leurs noms portent un préfixe hérité (par exemple
<prefixe-du-projet>_forge-pgdata), choisi pour ne jamais recréer un volume
vide. Pour le plan backend :
for v in $(awk '/^volumes:/{f=1} f && /^ name:/{print $2}' podman-compose.yml); do
podman volume exists "$v" || podman volume create "$v"
done
Faites de même avec podman-compose.vps2.yml. La liste des noms se lit avec
grep -n "name:" podman-compose.yml.
La boucle ne crée que les volumes absents : vous pouvez la relancer sans risque.
Résultat attendu : podman volume ls affiche ces noms et podman network ls les
deux réseaux. Si l'un d'eux manque, le premier lancement échoue : voir
Problèmes connus, section « Un conteneur ne démarre pas ».
PostgreSQL et Valkey du plan backend sont rattachés aux deux réseaux, avec
les alias DNS postgres et valkey sur mgaas-vps2 : les outils du plan
client (Odoo, Listmonk, n8n, Grafana, Mattermost, entre autres) les joignent par
ces noms.
Développement : démarrer les plans
# Plan backend
podman-compose -p chatbotaurus-vps1 up -d
# Plan client (passerelle MCP et serveurs des offres)
podman-compose -p mgaas-vps2 -f podman-compose.vps2.yml up -d
# Vérifier
podman ps --format "table {{.Names}}\t{{.Status}}"
Résultat attendu : chaque conteneur du plan affiche l'état Up, et ceux qui
déclarent une sonde de santé passent à healthy après leur délai de démarrage.
Les services des profils container-api et backup ne démarrent pas sans leur
profil : c'est normal. Un conteneur à l'état Exited, ou absent hors profil :
lisez podman logs <conteneur> et suivez Problèmes connus,
section « Un conteneur ne démarre pas ».
En développement, le binaire forge-api tourne sur l'hôte
(scripts/dev/run-forge-api.ps1) ; le service forge-api du compose est
derrière le profil container-api et ne démarre que sur demande :
podman-compose -p chatbotaurus-vps1 --profile container-api up -d forge-api
Services du plan backend (podman-compose.yml)
| Service | Conteneur | Rôle | Port publié (hôte → conteneur) |
|---|---|---|---|
postgres | forge-postgres | Base principale (image postgres:16-alpine) | 5432 |
valkey | forge-valkey | Cache et sessions | 6380 → 6379 (par défaut) |
qdrant | forge-qdrant | Base vectorielle | 6333 et 6334 |
ollama | forge-ollama | LLM locaux (limite mémoire fixée dans le plan compose) | 11434 |
authentik | forge-authentik | SSO / IAM | 9000 et 9443 |
forge-api | forge-api | Backend Rust (profil container-api) | 3000 |
openbao | forge-openbao | Secrets | 8200 |
pgadmin | forge-pgadmin | Administration PostgreSQL | 5050 |
victoriametrics | forge-victoriametrics | Métriques | 8428 |
victorialogs | forge-victorialogs | Journaux | 9428 |
faster-whisper | forge-faster-whisper | Transcription (STT) | 10300 → 8000 |
tts-models | forge-tts-models | Téléchargement des modèles vocaux | — |
kokoro-tts | forge-kokoro-tts | Synthèse vocale | 8880 |
livekit | forge-livekit | Temps réel (WebRTC) | 7880, 7881, 7882/udp |
crowdsec | forge-crowdsec | Détection d'intrusion | — |
seaweedfs | forge-seaweedfs | Stockage objet S3 (Apache-2.0) | 8333, lié à la boucle locale |
backup-agent | forge-backup-agent | Agent de sauvegarde (profil backup) | — |
tusd | forge-tusd | Réception TUS des archives (profil backup) | — |
Les sauvegardes sont décrites dans Sauvegarde et restauration.
Plan client (podman-compose.vps2.yml)
Le service mcp-gateway (binaire forge-gateway, port 8811) est le point
d'entrée du protocole MCP du plan. Autour de lui : les instances Odoo par
offre (mcp-odoo-therapie, mcp-odoo-medical, mcp-odoo-finance et les autres),
mcp-element, mcp-cryptpad, mcp-listmonk, mcp-n8n, mcp-grafana,
mcp-plausible, mcp-moodle, mcp-calrs, mcp-forgejo, mcp-mattermost,
mcp-paperless, mcp-frappe, mcp-searxng, mcp-typesense, mcp-umami,
mcp-matomo et leurs bases. La liste exacte se lit avec :
grep container_name podman-compose.vps2.yml
Production : unités Quadlet
En production, chaque conteneur est une unité Quadlet
containers/<service>/<service>.container, gérée par systemd sous le
compte <compte-deploiement>. Côté plan backend, on y trouve par exemple forge-traefik
(ingress), forge-api, forge-ui (fichiers statiques du front servis par
nginx), forge-openbao, forge-qdrant, forge-ollama, forge-livekit,
forge-authentik-server, forge-authentik-worker, forge-crowdsec et
forge-tusd. Sur ce plan, seuls l'ingress et le service temps réel publient des ports sur
toutes les interfaces ; les autres unités n'écoutent que sur la boucle locale
de l'hôte. Dans l'unité forge-api, le port 3000 n'est publié que sur la boucle
locale de l'hôte. Le détail des ports et des règles n'est pas publié. L'ingress du plan client est
mcp-caddy. Les unités forge-falco et forge-zeek existent dans
containers/, mais deploy-all.sh les a retirées de ses niveaux le
2026-09-19 : elles ne sont pas déployées.
Orchestration depuis un poste d'opérateur
containers/scripts/deploy-all.sh pilote les deux plans par niveaux
cumulatifs de 1 (démarrage) à 5 (plateforme).
Prérequis : sur le poste, bash, ssh, tar et curl ; sur chaque hôte cible, le
compte de déploiement non privilégié avec sudo, podman, Quadlet, python3, le mode
« linger » (voir Problèmes connus, section « Les
services Quadlet ne redémarrent pas après un redémarrage de l'hôte ») et le dossier
cible. L'étape preflight vérifie ces points. Les hôtes se désignent par
--hote-backend et --hote-client (alias SSH ou utilisateur@hôte), le dossier
déployé par --app. Les niveaux 4 et 5 sont refusés tant qu'une unité qu'ils exigent
n'existe pas dans containers/. Code de sortie : 0 si chaque étape demandée est
prouvée, 1 sinon, 2 pour une erreur d'usage.
# Lister ce que le niveau 1 déploie, avec ce que le code prouve
bash containers/scripts/deploy-all.sh --liste 1
# Voir les commandes sans se connecter
bash containers/scripts/deploy-all.sh 1 --plan backend --dry-run
# Étapes, dans cet ordre : preflight, expedier, images, cible, prouver (ou tout)
bash containers/scripts/deploy-all.sh 1 --plan backend --etape prouver
Le script ne contient aucun secret : ils naissent sur la cible
(scripts d'initialisation des secrets, exécutés sur l'hôte pendant l'étape cible,
de façon idempotente : relancer l'étape est sans danger). Résultat attendu de
l'étape prouver : un accès HTTPS, certificat vérifié, à chaque nom public du niveau ;
un nom qui ne résout pas imprime l'enregistrement DNS à créer chez votre hébergeur.
Déploiement tiré par l'hôte
Le chemin courant n'est pas une poussée depuis un poste : l'hôte tire.
Un minuteur systemd utilisateur lance un script 2 minutes après la fin du
passage précédent (OnUnitInactiveSec=120). Le script regarde si la branche
principale a avancé :
| Script | Rôle |
|---|---|
scripts/deploy/vps-autodeploy-front.sh | Construit le front (dx build --release) et bascule le dossier servi par forge-ui, puis prouve par HTTPS |
scripts/deploy/vps-autodeploy-backend.sh | Construit l'image backend sur l'hôte, met à jour l'unité forge-api, redémarre et prouve par HTTPS |
scripts/deploy/vps-autodeploy-vps2.sh | Déploie le plan client : seules les unités dont un fichier source a changé sont redémarrées |
Installation et suivi, depuis le poste de l'opérateur :
bash scripts/deploy/installer-autodeploy-hote.sh --vps1 # ou --vps2, --tout
bash scripts/deploy/suivre-autodeploy.sh
Les deux commandes ci-dessus se connectent en SSH : elles lisent l'hôte cible dans la
variable HOTE (l'installateur lit aussi HOTE_VPS2 pour le plan client) et la clé
SSH dans CLE. Posez ces variables explicitement avant de les lancer. Résultat attendu
de suivre-autodeploy.sh : l'état du minuteur (active), le SHA déployé, puis la fin
du journal et de la construction du front. Il ne lit pas l'état du backend :
comparez le champ sha de /api/v1/healthz au commit attendu (voir
Supervision).
Points à connaître :
- l'image backend est étiquetée par le SHA du commit
(
localhost/forge-stack:<sha-court>) et construite parpodman build -f Containerfile .sur l'hôte ; aucune image n'est supprimée, l'étiquette précédente reste disponible ; - si la santé locale n'est pas prouvée après le redémarrage, le script revient à l'image précédente ;
- un commit qui ne touche que la documentation ne reconstruit pas l'image ;
- aucune clé ni port n'est exposé par ce mécanisme : l'hôte tire, il ne reçoit rien.
La description complète du processus est dans
docs/deploiement/PROCESSUS-PAR-PUSH.md.
Construction des images
Le Containerfile à la racine construit en plusieurs étapes les binaires
Rust (forge-api, forge-cli, forge-ops) vers une image finale
minimale ; Containerfile.production en est la variante de production.
podman build -t localhost/forge-stack:dev -f Containerfile .
Le front se construit à part :
dx build --release --platform web --package forge-ui
Modèles Ollama
Une fois forge-ollama démarré, téléchargez les modèles dont vous avez
besoin :
podman exec forge-ollama ollama pull <nom-du-modèle>
podman exec forge-ollama ollama list
Le modèle utilisé par défaut se règle par la variable OLLAMA_MODEL du
backend. Le CLI forge-cli propose aussi une sous-commande model pull.
Le service forge-api du compose (profil container-api) fixe lui-même une valeur par
défaut de OLLAMA_MODEL (lisible dans podman-compose.yml) ; le binaire lancé sur
l'hôte suit, lui, les défauts de Variables d'environnement.
Téléchargez le modèle que votre backend demande. Résultat attendu : ollama list
affiche le modèle téléchargé.
Contrôle de la chaîne d'approvisionnement
- Le dépôt tient un SBOM CycloneDX par composant dans
docs/sbom/(python scripts/audit/sbom_cyclonedx.py --checkvérifie qu'il correspond auCargo.lock). - Le workflow de CI du dépôt analyse l'image backend avec Trivy (gravité HIGH et CRITICAL). Il est dormant : aucun exécuteur n'y est rattaché, il ne tourne pas aujourd'hui. Les exceptions acceptées sont listées dans un fichier d'exceptions motivées du dépôt. Un script d'analyse des images se lance à la main.
Notes importantes
- Les unités Quadlet de production sont préférées en mode rootless ;
binder les ports 80 et 443 en rootless exige de baisser
net.ipv4.ip_unprivileged_port_start(valeur à fixer au plus à 80, avec les droits administrateur ; l'en-tête de l'unité de l'ingress, dans les sources, donne le détail). - OpenBao se re-scelle au redémarrage : voir le guide d'exploitation du coffre (hors de ce livre) pour la topologie de descellement.
- Les fichiers compose portent des valeurs par défaut de développement
pour certains mots de passe : surchargez-les par l'environnement
(fichier
.env), ne les réutilisez jamais en production. En production, les secrets sont créés sur l'hôte par les scripts d'initialisation des secrets et ne figurent pas dans le dépôt.
Voir aussi
- Gestion des conteneurs — gestes du quotidien : journaux, redémarrage, mise à jour, dépannage
- Sauvegarde et restauration — les deux chaînes de sauvegarde et la répétition de restauration
- Problèmes connus — réseaux absents, conflits de port, OpenBao scellé, déploiement en retard
- Fichiers de configuration — fichiers Compose, unités Quadlet et ce que le code lit réellement
- Architecture Chatbotaurus — pourquoi deux plans et la règle de nommage des services