Chapitres
Sur cette page
DOC-04 / Référence technique · Chapitre 09
Déploiement & infrastructure
Décrit le pipeline de mise en ligne de Synedre OS et ses tenants, notamment l'asymétrie entre ./deploy (preprod/IA) et ./ship (production, gaté par flotte), le dispatcher YAML, le pattern build-host sans build VPS, et les doctrines associées aux secrets et commits.
Déploiement & infrastructure
Cette section décrit le pipeline de mise en ligne de la plateforme et de ses tenants : l'asymétrie entre les deux entrypoints de déploiement, le dispatcher piloté par configuration, le pattern build-local puis transfert (aucun build sur le VPS cible), le cas particulier du self-deploy du vaisseau-mère, et les doctrines associées (secrets, commit avant déploiement, gestion du drift de schéma).
1. Asymétrie ./ship vs ./deploy
Deux entrypoints coexistent à la racine du dépôt. Ils ne ciblent pas la même destination et n'ont pas le même propriétaire.
./deploy |
./ship |
|
|---|---|---|
| Cible | Préproduction (ou runtime live du vaisseau-mère / PROD pour les sites fondateurs sans préprod) | Production |
| Propriétaire | Agent / worker / cascade — systématique, sans demander | Gaté par la flotte : l'agent/tick peut shipper si le tenant l'autorise, sinon intervention manuelle requise |
| Git | Auto-commit du dirty ; reste sur la branche preprod |
checkout main → pull → merge preprod -X theirs → push origin main, puis retour preprod garanti (trap EXIT) |
| Garde-fou dirty | Auto-commit silencieux (sauf si on est sur main : refus dur) |
Bloquant : refus si le working tree est sale, sauf flag --allow-dirty |
| Drift check | Une seule cible préprod câblée ; tout autre tenant → skip explicite. Bypass via --skip-drift. |
Vérification du schéma du hub (bloquante) et de la base prod du tenant si la cible est connue. Pas d'équivalent --skip-drift : le drift hub est strictement bloquant. |
| Drift auto-apply | Le moteur de drift génère et applique en une transaction les DDL idempotentes manquantes (CREATE TABLE IF NOT EXISTS, ALTER TABLE ADD COLUMN IF NOT EXISTS) avant tout build ou rechargement. Activé par défaut ; désactivable ponctuellement via variable d'environnement. |
Le drift est appliqué manuellement avant le ship. |
| Smoke post-déploiement | Vérification HTTP des tenants, suivie d'un smoke visuel non bloquant (désactivable via variable d'environnement). | Vérification de l'environnement de process (anti-faux-positif) puis smoke HTTP + content/JSON checks. |
| Migrations DB | — | Affiche les fichiers .sql en attente dans le scope concerné — non bloquant, non auto-appliqué (voir §1ter). |
| Clôture | — | Propose la fermeture des chantiers en statut test (skip via --no-close). |
Doctrine :
./ship <tenant>cible la production, gaté par la flotte : si le tenant l'autorise, l'agent ou le tick peut shipper ; sinon, l'intervention manuelle est requise. Un ship nocturne non surveillé exige en outre une preuve QA verte../deploy <tenant>est toujours déclenché par l'agent, systématiquement et sans demander — y compris sur le vaisseau-mère lui-même, où./deployreconstruit le runtime live (pas de préprod distincte).
┌─────────────┐ ./deploy <tenant> ┌──────────────┐
IA → │ branche │ ─────────────────────→ │ préprod / │
│ preprod │ (auto, N fois) │ runtime live │
└─────────────┘ └──────────────┘
│
│ Revue préprod, validation
▼
┌─────────────┐ ./ship <tenant> ┌──────────────┐
ship → │ merge preprod│ ─────────────────────→ │ PRODUCTION │
│ → main │ (gaté flotte) │ │
└─────────────┘ └──────────────┘
Exception site public sans préprod : ./deploy est refusé pour le site personnel du fondateur — ce site vit directement en production sur le VPS CodeMyShop. On le modifie uniquement via ./ship.
Exception tenants fondateurs sans préprod (synedre.com, codemyshop.com, et un troisième site fondateur) : ./deploy pointe directement la production. Pour ces tenants, la frontière deploy = préprod ne tient pas — l'agent déclenche ./deploy, mais celui-ci modifie la production. C'est voulu ; il n'existe pas de préprod dédiée pour les sites fondateurs. ./ship reste disponible (fusion preprod → main + cérémonie complète) et est également gaté par la flotte.
1bis. Détail du pipeline ./ship (étapes numérotées)
./ship <tenant> enchaîne, dans l'ordre :
- Parsing des flags :
--allow-dirty(bypass garde-fou dirty),--no-close(skip post-ship) ; le reste est transmis au dispatcher. - Garde-fou dirty bloquant : vérification de la propreté du working tree — arrêt immédiat si sale (sauf
--allow-dirty). - Trap EXIT : retour sur la branche
preprodgaranti même en cas d'erreur ; un échec de checkout est signalé bruyamment (pour éviter de rester bloqué surmain). - Nettoyage des artefacts root : suppression des dossiers de sortie laissés par d'anciens builds avec des droits root, qui bloqueraient l'écriture.
- Drift hub (bloquant) : vérification du schéma du hub — aucun bypass possible. Suivi si applicable du drift de la base prod tenant.
- Fusion
preprod → main: checkout main, pull, merge avec stratégie theirs, push. - Migrations : affichage informatif des fichiers
.sqlen attente (voir §1ter) — non bloquant, non auto-appliqué. - Audit des agents : vérification de la cohérence de la flotte d'agents — non bloquant.
- Déploiement prod : appel du dispatcher de déploiement (voir §2).
- Smoke prod : vérification de l'environnement de process, puis smoke HTTP + content/JSON checks.
- Post-ship close : proposition de fermeture des chantiers en statut
test(skip via--no-close).
Asymétrie
--skip-drift:./deployaccepte--skip-driftpour bypasser le drift check en préprod (cas de hotfix CI urgent)../shipn'a aucun équivalent — le drift du hub y est strictement bloquant : on ne ship jamais en production sur un schéma divergent.
1ter. Migrations DB lors du ship
Le ship affiche les fichiers .sql en attente mais ne les applique jamais automatiquement. Le runner automatique de migrations a été désactivé et aucun runner alternatif n'est branché — c'est le point dur réel. Le bloc est purement informatif (non bloquant) : il imprime les instructions à exécuter manuellement.
Un mapping relie chaque argument tenant à un scope de migrations, afin d'éviter de mélanger les migrations du vaisseau-mère et celles des tenants :
| Argument tenant | Scope de migrations |
|---|---|
| vaisseau-mère / runtime OS | mothership |
| tenant A (boutique e-commerce) | tenant-a |
| tenant B (vape shop) | tenant-b |
| site fondateur 1 | scope dédié |
| site personnel fondateur | scope dédié |
| plateforme CodeMyShop | scope dédié |
all |
tous les scopes (cas spécial) |
| autre | fallback : scope = argument passé |
Cas spécial all : agrège tous les fichiers .sql de tous les scopes, en excluant les sous-dossiers _applied/ et applied/ (migrations déjà passées). Les autres scopes listent simplement les fichiers du dossier concerné.
2. Routage de ./deploy et ./ship par tenant
Chaque argument tenant est routé vers un script dédié ou vers le dispatcher générique de déploiement.
| Tenant (argument) | ./deploy → |
./ship → |
|---|---|---|
| vaisseau-mère (défaut) | Vérification du verrou de santé, puis script de self-deploy du vaisseau-mère (--prod --all) |
Idem script self-deploy (--prod --all) |
| Site personnel fondateur | Refusé — pas de préprod | Dispatcher générique |
| synedre.com | Dispatcher générique → PROD direct | Dispatcher générique ⚠️ — la vérification de l'environnement de process ne reconnaît pas ce tenant (bug latent), ce qui bloque le smoke |
| CodeMyShop | Dispatcher générique → PROD direct (plus de préprod) ; smoke activé | Dispatcher générique |
| CodeMyShop demo | Dispatcher générique → staging demo | — |
| Tenant A (e-commerce) | Dispatcher générique → préprod ; smoke désactivé (préprod derrière authentification basique) | Dispatcher générique → prod |
| Tenant B (vape shop) | Dispatcher générique → préprod ; smoke activé | ⚠️ Tombe dans le fallback → script Ansible legacy (probablement en erreur, pas de VPS prod configuré pour ce path) |
| Site fondateur 1 | Dispatcher générique → PROD ; smoke activé | Dispatcher générique |
| all | 5 jobs en parallèle en arrière-plan : vaisseau-mère, CodeMyShop (PROD), tenant A (préprod), tenant B, site fondateur 1. Smoke post-jobs. ⚠️ Le smoke du mode all vérifie le staging démo de CodeMyShop, pas le site vitrine prod. |
3 jobs en parallèle : vaisseau-mère (--prod --all), site personnel fondateur, CodeMyShop. Smoke sur les 3 cibles. |
--list / -l |
Dump de la table d'inventaire des VPS (voir §6) | — |
| autre | Fallback : script Ansible legacy → préprod | Fallback : script Ansible legacy → prod |
Convergence vers le dispatcher unique : à l'issue d'un travail de standardisation et d'une purge des scripts legacy, tous les tenants distants sauf le vaisseau-mère passent par le dispatcher unique pour
./deployet pour./ship. Le vaisseau-mère conserve son script dédié car le self-deploy est architecturalement distinct : build Docker local, sans transfert réseau vers un VPS externe.
Incohérence smoke en mode
all:./deploy alldéploie CodeMyShop en production directe, mais le smoke suivant vérifie le staging démo. La vitrinecodemyshop.comn'est pas vérifiée dans ce chemin — point à corriger dans la boucle smoke du modeall.
Le dispatcher piloté par YAML
Le script de déploiement standard gère l'ensemble des projets clients distants. Il accepte un nom de tenant, une cible optionnelle et un flag global, avec un flag de nettoyage de cache facultatif. Sa spécification de schéma est maintenue dans un document dédié, régulièrement complété d'exemples canoniques couvrant les deux variantes de runtime (conteneurisée et processus supervisé) ainsi que les configurations multi-cibles.
Résolution du fichier de configuration
Au démarrage, le dispatcher résout le fichier YAML à utiliser selon deux règles :
- Si le tenant désigné correspond au vaisseau-mère lui-même, le dispatcher cherche un fichier de configuration dédié — ce branchement est documenté mais reste inerte en pratique : le fichier correspondant n'existe pas sur disque, car le vaisseau-mère dispose de son propre pipeline de self-déploiement, distinct du dispatcher standard.
- Pour tout autre tenant, le dispatcher charge le fichier
deploy.yaml(oudeploy.<cible>.yamlsi une cible est spécifiée) situé dans le répertoire du tenant au sein de l'arborescence des projets.
Note : Le nom de tenant correspondant à synedre.com (site fondateur) est traité comme un tenant ordinaire et charge son propre fichier de configuration — il ne déclenche pas le branchement vaisseau-mère.
Étapes du pipeline de déploiement
- Analyse des arguments : le flag de cible (avec séparateur
=obligatoire), le flag global et le flag de nettoyage sont extraits et validés. - Résolution et validation du YAML : un utilitaire Python analyse le fichier de configuration et émet les variables de déploiement sous forme de paires clé-valeur évaluées par le shell. Toute erreur de validation interrompt immédiatement le pipeline.
- Parse des arguments standards : extraction des indicateurs de nettoyage de cache et de déploiement global.
- Bannière de début : affichage du récapitulatif de déploiement et initialisation du chronomètre.
- Vérification de dérive de schéma (si la section
driftest présente) : comparaison entre le schéma attendu et l'état réel de la base de données. - Application automatique de la dérive (si la section
driftest présente) : génération et application en une seule transaction des instructions DDL idempotentes manquantes — création de tables et ajout de colonnes si absentes. Cette étape est active par défaut et s'exécute avant tout build ou rechargement. Elle peut être désactivée ponctuellement via une variable d'environnement dédiée. - Hooks en arrière-plan : lancement de l'audit des agents en tâche de fond.
- Génération des routes i18n (pré-build) (si la section
seed_i18nest présente) : interrogation de la base de données du tenant via la connexion distante pour lire les segments de routes localisés, puis écriture du fichier JSON consommé par le module d'internationalisation au moment du build. Cette étape est non bloquante : si la base est inaccessible, le build retombe sur les valeurs codées en dur. - Build Nuxt : compilation de l'application front-end.
- Seed i18n (si applicable) : injection des données de traduction.
- Source maps (si le hook correspondant est activé) : envoi des cartes sources.
- Empaquetage : création d'une archive compressée du build dans le répertoire temporaire du système.
- Push Git en arrière-plan (si le hook est activé).
- Upload de l'archive : transfert vers le VPS client par copie sécurisée.
- Rechargement distant, dispatché selon la variante déclarée :
pm2: rechargement graceful ou redémarrage forcé du processus supervisé.docker: redémarrage du service via Docker Compose.
- Installation des automates récurrents (si la section
cronest présente) : pose des tâches planifiées dans le crontab de l'utilisateur SSH sur le VPS client (jamais sur le vaisseau-mère). Chaque entrée est délimitée par un bloc marqué idempotent : l'ancien bloc est retiré avant réécriture. Une liste vide déclenche le nettoyage du bloc existant. Doctrine : les automates récurrents s'exécutent au plus près de leur base de données. - Attente des hooks en arrière-plan : synchronisation sur le push Git et l'audit des agents.
- Health check : boucle de vérification de disponibilité de l'URL cible.
- Bannière de fin : affichage du bilan et de la durée totale.
Schéma du fichier de configuration
Chaque projet client est décrit par un fichier YAML validé par l'utilitaire Python dédié. Les sections sont les suivantes :
| Section | Obligatoire | Description |
|---|---|---|
name |
Oui | Identifiant kebab-case du déploiement (lettres minuscules, chiffres, tirets, underscores). |
build |
Oui | Chemin relatif du client local (requis) ; environnement Node (défaut : production). |
ssh |
Oui | Hôte cible (requis) ; utilisateur SSH (défaut : ubuntu) ; clé SSH optionnelle. |
drift |
Non | Déclaration des tables et colonnes attendues pour la vérification et l'application de dérive de schéma. |
seed_i18n |
Non | Paramètres de génération des routes localisées depuis la base de données. |
remote |
Oui | Variante de runtime (pm2 ou docker) et paramètres associés. |
health |
Oui | URL de vérification (requise) ; délai maximal d'attente (défaut : 45 s). |
hooks |
Non | Activation optionnelle de : audit des agents, envoi des source maps, push Git. |
cron |
Non | Liste d'automates récurrents à installer sur le VPS client (entrées : clé, schedule, commande, commentaire). |
Garde-fous de cohérence par variante
L'utilitaire de validation applique des règles de cohérence strictes selon la variante choisie :
- Variante
pm2: le nom de l'application supervisée est requis ; les paramètres propres à Docker (nom de conteneur, sous-répertoire) sont interdits. - Variante
docker: le nom du conteneur est requis ; les paramètres propres à PM2 sont interdits.
Validation des automates récurrents
Chaque entrée de la section cron est validée individuellement : la clé doit être en kebab-case, le schedule doit comporter exactement cinq champs au format crontab standard, et la commande ne peut être vide. Une section cron déclarée vide est néanmoins émise par le validateur — ce signal indique à l'installeur de nettoyer intégralement le bloc d'automates du tenant sur le VPS cible.
Exemple de fichier de configuration
Voici la structure d'un fichier de configuration type pour un projet utilisant la variante PM2 :
name: mon-projet
build:
local_client: codemyshop/tenants/mon-projet
node_env: production
ssh:
host: <adresse du VPS client>
remote:
variant: pm2
dir: /var/www/codemyshop/app/codemyshop/tenants/mon-projet
pm2_app: mon-projet-nuxt
pm2_remote_user: codemyshop
health:
url: https://mon-projet.com
hooks:
git_push: mon-projet
Rappel : L'adresse de l'hôte SSH n'est jamais versionnée en clair dans un fichier partagé — elle est fournie via une variable d'environnement ou un fichier de configuration local exclu du contrôle de version.
Pattern build-host, compression et transfert (aucun build sur le VPS)
L'invariant central pour les déploiements distants est le suivant : le VPS ne compile jamais. Le bundle de l'application front-end est entièrement construit sur la machine hôte (le vaisseau-mère), compressé, transféré, extrait sur le VPS cible, puis le processus applicatif est rechargé. La bannière de démarrage affiche explicitement le mode BUILD HOST pour confirmer cet invariant à chaque lancement.
┌──────────────── MACHINE HÔTE (vaisseau-mère) ──────────────────┐
│ 1. Installation des dépendances (delta, cache local) │
│ 2. Tests de smoke invariants URL (bloquants) │
│ 3. Build de l'application front-end → .output/ │
│ 4. Compression du répertoire .output en archive tar+pigz │
└────────────────────────────┬───────────────────────────────────┘
│ transfert sécurisé (scp)
▼
┌──────────────────────── VPS CLIENT ────────────────────────────┐
│ 5. Mise de côté de l'ancien .output (point de rollback) │
│ 6. Extraction de l'archive (décompresseur auto-détecté) │
│ 7. Rechargement du processus (graceful reload ou restart) │
│ 8. Si en ligne → purge .output_old sinon → exit 1 + conseil │
└────────────────────────────┬───────────────────────────────────┘
│
▼ sondage HTTP jusqu'à 200 (délai configurable)
health check
Étapes détaillées du pipeline
- Build front-end : le répertoire de sortie
.outputest purgé (et.nuxtégalement si le flag--cleanest activé). Les dépendances sont installées en mode hors-ligne depuis le cache local. Un smoke test bloquant valide les invariants de forme des URL produit avant tout build ; en cas d'échec il annule immédiatement le déploiement. Le build s'effectue ensuite avec retry automatique sur clean si la compilation incrémentale échoue. - Compression : l'archive est créée depuis le répertoire
.outputavecpigzsi disponible sur l'hôte, sinongzipen repli systématique. - Transfert : l'archive est copiée vers le répertoire temporaire du VPS via
scp. L'archive locale est supprimée immédiatement après le transfert. - Redémarrage Docker : connexion SSH, rotation
.output → .output_old, extraction de l'archive dans le répertoire applicatif, suppression d'un module natif CommonJS connu pour provoquer des artefacts de tree-shaking, redémarrage du container, vérification que le container est bien en étatrunning— sinon rollback. - Rechargement PM2 graceful :
pm2 reload <app> --update-envconserve les connexions en cours. Un lien symbolique optionnel peut être recréé vers le dossier des fichiers statiques pour éviter les erreurs de résolution de fichiers sur les routes de back-office. - Redémarrage PM2 hard : utilisé uniquement au premier déploiement ou lorsque la configuration PM2 est explicitement modifiée — séquence
delete+start+save. - Health check en boucle : sondage HTTP de l'URL principale jusqu'à obtention d'un code
200, avec délai d'attente maximal configurable (défaut 45 s). - Installateur cron par tenant : une étape optionnelle installe ou met à jour les tâches planifiées propres à chaque client sur le VPS distant.
Stratégie de rollback
Le répertoire .output_old est conservé pendant toute la durée du rechargement. S'il réussit, il est purgé automatiquement. En cas d'échec — process ou container non online — le script affiche uniquement la commande de restauration suggérée et termine avec le code exit 1. La restauration n'est jamais exécutée automatiquement : l'opérateur doit procéder manuellement au remplacement de .output par .output_old. Ce comportement est identique pour les pipelines PM2 graceful, PM2 hard et Docker. La variable d'environnement SHIP_KEEP_ROLLBACK=1 force la conservation de .output_old même en cas de succès.
Smoke post-déploiement : trois couches de validation
Le script de smoke intègre, depuis début juin 2026, deux sous-niveaux de vérification automatique, complétés par une troisième couche visuelle.
-
Vérifications HTTP : chaque URL de chaque tenant couvert est sondée via
curl. Un code5xxest bloquant ; un code4xxgénère un avertissement non-bloquant. La boucle globale couvre l'ensemble des tenants enregistrés ; certains tenants sont également sondés individuellement depuis l'orchestrateur de ship. -
Vérifications de contenu JSON : au-delà du code HTTP, la forme du JSON retourné par l'API de navigation est validée structurellement. Cette couche a été ajoutée après un incident (mai 2026) où un déploiement rendait HTTP
200pendant deux jours mais la réponse JSON contenait un champ d'erreur — la navigation était vide et le pied de page affichait des clés brutes. Sans ce niveau, les ruptures SSR data-driven pouvaient passer totalement invisibles. - Smoke visuel multimodal (non-bloquant, depuis juin 2026) : un agent de vérification capture un screenshot Playwright de la page déployée et produit un verdict par analyse d'image. Si aucune configuration de checks visuels n'est présente pour un tenant donné, l'étape est sautée proprement, sans latence. Actif par défaut, désactivable via variable d'environnement.
Garde-fous d'environnement avant smoke
Vérification des variables d'environnement du processus distant
Un code HTTP 200 ne suffit pas à valider un déploiement PM2. Un incident de mai 2026 l'a démontré : un tenant redéployé sans ses variables de connexion à la base de données retournait 200 sur toutes les routes SSR, car la page rendait un squelette vide mais syntaxiquement valide. Toutes les routes data-driven (navigation, pied de page, avis, internationalisation) jetaient des erreurs 500 en silence pendant deux jours.
C'est pourquoi l'orchestrateur de ship lance un vérificateur d'environnement SSH avant le smoke : il se connecte au VPS distant et inspecte les variables critiques réellement chargées dans l'environnement du processus PM2 en cours d'exécution. Codes de retour : 0 si tout est présent, 1 si des variables sont manquantes (bloquant), 2 si la connexion SSH ou le processus PM2 est inaccessible (bloquant). Les cibles non-PM2 (vaisseau-mère, environnements non concernés) reçoivent un exit 0 immédiat.
Vérification de cohérence du lockfile avant build
Avant tout déploiement de l'application principale du vaisseau-mère, un vérificateur de cohérence du lockfile est appelé. Il détecte deux skews bloquants connus :
- Une version de l'outil de minification CSS inférieure au seuil requis dans le lockfile (dépendances PostCSS transitives manquantes → build cassé silencieusement).
- Une version du framework Nuxt inférieure au seuil requis dans le manifeste du projet (risque de skew avec le bundler).
Un avertissement non-bloquant est émis si des surcharges de version de Vite sont présentes dans la configuration racine. En cas d'erreur bloquante, le script affiche un message correctif explicite (suppression du lockfile et réinstallation propre) et termine avec exit 1, annulant le déploiement.
Cas particulier : auto-déploiement du vaisseau-mère
L'application cockpit du vaisseau-mère ne passe pas par le pipeline build-host/upload décrit ci-dessus. Il s'agit d'un auto-déploiement local : le vaisseau-mère compile à l'intérieur de son propre container applicatif.
Différences avec les tenants distants
- Pas d'environnement de pré-production depuis mai 2026 :
./deploysur l'application vaisseau-mère est un rebuild direct en production live du cockpit, sans la cérémonie de fusion de branches de./ship. - Transfert local par
docker cp: un tar source minimal (code applicatif + manifestes de dépendances) est copié dans le container. Le tar exclut explicitement les répertoires de cache et de build (.nuxt,.output,node_modules). La clé de performance est la préservation du cache.nuxtcôté container : il est mis de côté avant l'extraction du nouveau code source, puis restauré, permettant un build incrémental plutôt qu'un cold build. - Build dans le container : la commande de build est exécutée à l'intérieur du container en cours d'exécution. L'ancien serveur reste actif pendant toute la durée du build. Après le build, le container est arrêté proprement, le nouveau répertoire
.outputest copié sur l'hôte, puis le container est recréé avec--force-recreatepour que les variables d'environnement soient relues depuis le fichier de configuration. - Architecture standalone : l'application vaisseau-mère n'étend plus le noyau partagé des boutiques. Le tar source exclut donc l'ensemble du code boutique, réduisant significativement la taille du transfert (gain d'environ 16 MB).
- Fast-path npm : une empreinte
sha256du lockfile est conservée dans le container. Si le lockfile n'a pas changé depuis le dernier déploiement, l'étapenpm installest intégralement sautée. - Health check par UUID de build : au lieu d'un simple sondage HTTP
200, le health check compare l'UUID du nouveau build (lu dans le manifeste.outputcôté hôte) avec celui effectivement servi par l'endpoint de santé du container. Cela garantit que c'est bien le nouveau build qui est actif, et non une version précédente maintenue par un hot-reload partiel. - Variante accélérée opt-in : une variable d'environnement permet d'activer un pipeline alternatif — build sur l'hôte + swap atomique du répertoire dans le container — offrant un gain d'environ 35 % sur la durée totale du déploiement.
./shipsur l'application vaisseau-mère ajoute la cérémonie complète (fusion de branches, push, audit des agents, smoke production, clôture post-ship), mais appelle en fin de parcours le même script de déploiement local.
Inventaire de la topologie — registre central des environnements
Le système maintient un registre central unique de l'ensemble des environnements déployés : VPS clients, stacks applicatives, domaines publics et métadonnées de criticité. Ce registre est la seule source de vérité autorisée pour la topologie ; tout document d'architecture rédigé à la main peut être obsolète et ne fait pas foi.
La commande ./deploy --list interroge ce registre et affiche un tableau formaté de tous les environnements actifs, triés par criticité décroissante. Chaque ligne porte : l'identifiant de l'environnement, son type (production, staging, infra, legacy, audit), son domaine public, et la nature de sa stack (rendu serveur Nuxt ou moteur e-commerce).
Structure du registre
Chaque entrée du registre expose les colonnes suivantes :
| Champ | Rôle |
|---|---|
| Identifiant d'environnement | Clé logique primaire de l'entrée |
| Type d'environnement | production / staging / infra / legacy / audit |
| Coordonnées d'accès SSH | Adresse du VPS cible, utilisateur et chemin de clé — jamais exposés en clair dans les fichiers versionnés |
| Domaine public | URL publique de l'environnement |
| Base de données associée | Références au conteneur DB, nom de base, utilisateur ; le mot de passe n'est jamais stocké ici — seul le nom de la variable d'environnement qui le porte est référencé |
| Runtime web | Nom du conteneur web, présence d'un moteur e-commerce, présence d'un rendu Nuxt |
| Criticité & facturation | Niveau de criticité opérationnelle, MRR, offre commerciale |
| Autorisation de déploiement automatique | Booléen indiquant si un ship automatique est permis — certains environnements exigent une validation humaine explicite |
| Codename de déploiement | Identifiant à copier pour lancer la commande exacte (voir la section Avant chaque déploiement ci-dessous) |
| Référence client | Clé étrangère vers le registre fédéré des clients (fédération introduite en mai 2026) |
État de la flotte et cas particuliers
Le registre compte actuellement une dizaine d'environnements actifs, couvrant des stacks variées (Nuxt, e-commerce, infra). Quelques situations méritent une attention particulière :
- Environnements orphelins en production : certains environnements portent le type production bien qu'ils soient en sommeil (projets sans activité commerciale courante). Ils ne doivent pas être regroupés sous l'étiquette legacy — leur type réel est production.
- Double entrée possible : un même projet peut apparaître sous deux lignes distinctes (par exemple une ligne production / Nuxt et une ligne legacy / sans Nuxt) lorsque deux générations coexistent. Ne pas les fusionner.
- Décommissionnement : un environnement décommissionné passe à
active=0et disparaît des listes actives. Son dossier et ses secrets sont retirés ; l'historique client est conservé dans le registre fédéré.
Avant chaque déploiement
Avant de lancer ./deploy ou ./ship, consulter la fiche de l'environnement cible pour y copier le codename de déploiement exact. Cette précaution évite toute ambiguïté sur l'environnement visé et garantit que la commande s'appuie sur les paramètres à jour du registre plutôt que sur une valeur mémorisée ou un document d'architecture potentiellement obsolète.
Doctrine des secrets — cinq niveaux de portée
Chaque secret possède un fichier canonique unique. La présence d'une même clé dans deux fichiers distincts est considérée comme un bug à corriger immédiatement.
Les cinq niveaux
| # | Portée | Description | Versionné |
|---|---|---|---|
| 1 | Tenant unique | Secrets propres à un seul environnement client — lus par son conteneur applicatif et son pipeline de déploiement | Non |
| 2 | Vaisseau-mère — cœur applicatif | Secrets du cockpit et du conteneur Nuxt principal | Non |
| 3 | Vaisseau-mère — scripts hôte | Secrets consommés par les scripts d'automatisation, les crons et les connexions SSH aux VPS clients | Non |
| 4 | Cross-projet | Secrets partagés entre tous les projets : clés API des fournisseurs IA, clé de chiffrement applicative, configuration SMTP principale — stockés hors dépôt sur la machine hôte | Non (hors dépôt) |
| 5 | Templates publics | Fichiers .env.example — contiennent uniquement les noms de variables, jamais de valeurs réelles |
Oui |
Ordre de chargement et priorité
Le conteneur applicatif principal charge les fichiers de secrets dans cet ordre : fichier cross-projet → fichier cœur → fichier hôte. En cas de doublon entre le fichier cœur et le fichier hôte, le fichier hôte a la priorité. Conséquence directe : ne jamais déclarer la même variable dans les deux niveaux.
Règle anti-fuite P0
Aucun secret en clair ne doit figurer dans un fichier versionné. Quand un fichier versionné doit faire référence à un secret, il cite uniquement le nom de la variable d'environnement et le fichier .env* qui la porte — jamais la valeur. Exemples d'application :
- Le pipeline de déploiement référence le nom de la variable portant le mot de passe de base de données, pas le mot de passe lui-même.
- Les coordonnées SMTP et IMAP vivent dans le fichier hôte ; seuls leurs noms de variables sont cités dans la configuration versionnée.
- Le registre central des environnements stocke le nom de la variable d'environnement du mot de passe DB — jamais sa valeur.
Note : La vue résumée de l'architecture décrit trois niveaux (infra / cœur / tenants). La doctrine complète en distingue cinq en ajoutant le niveau cross-projet et les templates publics. En cas de divergence, la doctrine détaillée prévaut.
Règle de commit avant déploiement
Un incident passé a mis en évidence le risque suivant : un correctif appliqué localement mais jamais commité peut être silencieusement écrasé lors d'un déploiement ultérieur. Le pipeline de déploiement effectue un git checkout du répertoire cible avant de pousser les fichiers — tout fichier modifié localement mais non versionné est donc perdu. Cet incident a provoqué quatre heures d'indisponibilité en production.
Comportement du garde-fou
Un script de vérification de l'état du dépôt est exécuté avant chaque opération. Il retourne trois états possibles : dépôt propre, dépôt avec modifications non commitées, ou répertoire hors-dépôt. La vérification est scopée par cible : les modifications d'un fichier du vaisseau-mère ne bloquent pas le déploiement d'un tenant, et inversement.
- Lors d'un
./deploy: si des modifications non commitées sont détectées, le système effectue un commit automatique silencieux (libellé standardisé, horodaté) suivi d'un push en arrière-plan. Exception : si la branche courante estmain, le déploiement est refusé — aucun commit automatique n'est réalisé sur la branche principale. - Lors d'un
./ship: si des modifications non commitées sont détectées, l'opération est bloquée avec un message explicite invitant à commiter puis relancer. Un drapeau de contournement existe pour les correctifs d'urgence volontaires uniquement.
Hook de fin de session
Un hook déclenché en fin de session vérifie également l'état du dépôt et bloque la clôture si des modifications non commitées subsistent. La règle est absolue : aucun travail terminé ne reste non commité. L'agent commite ; l'opérateur humain ne tape jamais les commandes git add / git commit manuellement.
Fichiers de référence
| Composant | Rôle |
|---|---|
| Point d'entrée de déploiement préprod | Orchestre le routage vers le bon tenant en préprod |
| Point d'entrée de déploiement production | Cérémonie complète de mise en production, avec gestion de la flotte et des autorisations par environnement |
| Dispatcher YAML-driven | Traite les déploiements distants (tenants externes au vaisseau-mère) à partir d'un fichier de configuration déclaratif |
| Bibliothèque de helpers de déploiement | Fonctions réutilisables : build, packaging, upload, rechargement, vérification de santé, bannière, cron |
| Parseur de configuration déclarative | Lit et valide le fichier de configuration de déploiement ; expose les paramètres aux scripts (sections : build, SSH, détection de dérive, internationalisation, distant, santé, hooks, cron) |
| Script de self-déploiement du vaisseau-mère | Déploie le conteneur applicatif principal en interne (build dans le conteneur, arrêt / copie / redémarrage) |
| Variante de déploiement accéléré | Build sur la machine hôte puis swap atomique — réduit le temps d'indisponibilité |
| Garde-fou état du dépôt | Vérifie la propreté du dépôt avant déploiement ou ship (voir section précédente) |
| Garde-fou de cohérence des dépendances | Vérifie l'absence de dérive dans le fichier de verrouillage avant déploiement du vaisseau-mère |
| Script de smoke tests HTTP | Vérifie les réponses HTTP et le contenu/JSON après déploiement sur l'ensemble des environnements actifs |
| Vérificateur d'environnement de processus | Contrôle la cohérence des variables d'environnement des processus de rendu avant les smoke tests en production |
| Schéma de configuration déclarative | Spécification du format de fichier de déploiement avec exemples canoniques |
| Runbook opérationnel | Procédures SSH, gestion des processus, Docker, renouvellement de certificats |
| Doctrine des secrets | Référence des cinq niveaux de secrets (voir section dédiée ci-dessus) |
| Registre central des environnements | Source de vérité unique de la topologie (voir section dédiée ci-dessus) |
Avertissement : Le document de workflow historique est partiellement obsolète. Il décrit encore une infrastructure de préprod et un pipeline de mise en production démantelés en mai 2026. En cas de conflit entre ce document et le code source effectif, le code fait foi.