J'ai voulu ramener notre pile de douze conteneurs à quatre. Deux de mes trois conclusions étaient fausses.
SOIT Team

Les projets auto-hébergés perdent la plupart de leurs futurs utilisateurs dès la première commande du README, et la nôtre nomme douze services sur une seule ligne. Le ticket #25 posait la question évidente : je veux juste y jeter un œil, ai-je vraiment besoin de tous ? La réponse est non. Sept des douze suffisent et, comme trois de ces sept sont des tâches uniques qui se terminent d'elles-mêmes, seuls quatre conteneurs restent permanents : postgres, minio, api et web. Le plus utile de l'exercice reste pourtant que la pile réduite a été réellement exécutée plutôt que raisonnée sur le papier. Deux des trois conclusions tirées de la lecture du code se sont révélées fausses, et les deux fausses étaient précisément celles qui auraient laissé un nouveau venu devant une URL morte.
Le moyen le plus rapide de savoir si une dépendance est réelle n'est pas le guide de déploiement mais le health check, car un guide documente une intention tandis qu'un health check documente un comportement. Dans le point de terminaison de readiness, trois backends sont traités différemment : si la base de données ne répond pas, on lève un 503 ; si le stockage objet ne répond pas, on lève un 503 ; si le magasin vectoriel ne répond pas, le champ indique simplement indisponible et le point de terminaison renvoie tout de même 200. Cette asymétrie nomme les exigences dures sans détour : un Postgres joignable et une racine de stockage accessible en écriture. Tout le reste est négociable en principe, et la suite de ce parcours porte sur les négociations qui survivent au contact de l'image publiée.
Racine de stockage n'est pas synonyme de MinIO, et l'adaptateur se résout bien vers un URI de fichier local lorsque rien d'autre n'est configuré, d'où la première conclusion : le magasin d'objets pouvait partir. Il ne peut pas. En pointant le stockage vers un répertoire local, l'API démarrait en annonçant que le stockage objet est indisponible, et construire l'adaptateur à la main dans le conteneur a donné la vraie erreur : permission refusée sur un chemin que personne n'avait demandé. La cause est une fonction de normalisation qui retire les barres obliques aux deux extrémités du chemin racine, transformant un chemin absolu en chemin relatif ; ce chemin relatif est ensuite résolu par rapport au répertoire de travail de l'image, que le Dockerfile copie en tant que root alors que le processus tourne sans privilèges. Monter un volume ne sauve rien non plus, Docker créant le point de montage également en root. MinIO reste donc, ce qui est peu coûteux pour deux cents mégaoctets, et le bug sous-jacent est enregistré sous le ticket #43.
Le groupe vectoriel s'en va en bloc, et il vaut la peine d'être précis sur ce que cela coûte. Milvus dépend d'etcd pour les métadonnées et de MinIO pour les données, ces noms voyagent donc ensemble. L'API démarre quand même sans eux, parce que la connexion vectorielle est établie paresseusement au premier usage et non pendant l'injection de dépendances. Ce qu'elle ne fait pas, c'est se dégrader en résultats vides : l'implémentation vectorielle en mémoire est réservée aux tests et, contrairement au port des secrets, elle ne consulte jamais la variable d'environnement. Résultat : la plateforme démarre, les fonctions non vectorielles marchent, readiness signale honnêtement le magasin vectoriel comme indisponible tout en se déclarant prête, et la recherche documentaire lève une erreur dès qu'on s'en sert. Cette conclusion-là a tenu.
La deuxième conclusion erronée est celle qu'aucune lecture n'aurait produite. Cette réponse honnête de readiness a mis trente-quatre secondes à revenir, parce que la sonde vectorielle doit résoudre un nom d'hôte qui n'existe plus et que rien ne borne son attente. La sonde a été écrite pour échouer en douceur, pas pour échouer vite. Pendant ce temps, Compose vérifie la santé de l'API avec une requête de trois secondes et une marge de cinq, si bien que la vérification ne peut pas passer et que le conteneur reste marqué en permanence comme non sain alors même que le service fonctionne et accepte les connexions. Le conteneur web déclare dépendre de la bonne santé de l'API : dans un démarrage normal, l'interface ne se lance donc jamais. Le correctif tient en une option, démarrer web en ignorant les dépendances, et le délai manquant sur la sonde est enregistré sous le ticket #44.
Trois services sont réellement optionnels, chacun pour une raison différente. Vault peut rester éteint : laisser son URL et son jeton vides bascule vers un magasin de secrets en mémoire de processus, ce que le câblage autorise en environnement de développement, au prix qu'annonce son propre nom, à savoir que les secrets ne survivent pas à un redémarrage. Redis n'est pas une question mais trois, aux réponses distinctes : le bus d'événements peut revenir à son implémentation en mémoire, qui ne délivre qu'au sein d'un seul processus ; le cache de permissions se dégrade proprement, un Redis injoignable se lisant comme un défaut de cache et l'appelant repassant par la base ; et le limiteur de débit n'a aucun équivalent en mémoire, mais il n'est appelé que si une limite par outil ou un quota quotidien est configuré. Retirer Redis d'une démo est donc sans risque tant que vous ne configurez pas ces deux-là, seul point de cette liste qui dépende de ce que vous comptez montrer.
Un service n'est pas supprimé mais replié. Le dispatcher d'outbox exécute l'outbox transactionnel, et son réglage détermine où ce travail a lieu, non s'il a lieu : compose désactive le chemin intégré au processus et fait tourner la même logique dans un conteneur dédié, et une seule variable d'environnement inverse cela pour une démo. La production fait l'inverse et l'impose dans le code plutôt que de le conseiller dans la documentation, car la distribution et le traitement des requêtes dans un même processus se disputent les mêmes ressources et un redémarrage interrompt les deux à la fois. Quant au worker d'ingestion de connaissances, il n'a qu'une tâche et aucune raison d'exister si votre démo ne téléverse jamais de document ; et tant qu'on y est, les lourdes dépendances d'apprentissage automatique qu'on lui prête vivent en réalité dans un autre groupe optionnel.
Rien de tout cela n'est une forme de déploiement supportée, et il vaut la peine de dire pourquoi la configuration par défaut en demande douze. Chaque service retiré ici correspond à quelque chose que la production impose au démarrage : la validation d'exécution refuse de démarrer sans le bus d'événements Redis, sans le processus dispatcher dédié, sans un vrai gestionnaire de secrets, sans télémétrie et sans vérification des signatures de plugins. La topologie par défaut vise la forme de production, pas celle de la démonstration, et la distance entre les deux se mesure finalement en une poignée de variables d'environnement, ce qui se trouve être le chemin le plus rapide pour comprendre l'architecture. Gardez aussi l'autre leçon : lire le code donne des hypothèses, l'exécuter donne des conclusions. Le dépôt est sur github.com/soit-ai/soit, le quickstart complet dans docs/quickstart.md, et si vous bloquez sur une étape de la version réduite, le gestionnaire de tickets est l'endroit pour le dire.