Chapitres
Sur cette page
DOC-05 / Référence technique · Chapitre 09
Déploiement & infrastructure
Décrit le pipeline de mise en ligne Synedre OS : asymétrie entre ./deploy (IA, preprod) et ./ship (Alex, production), le dispatcher YAML, le pattern build-host sans build VPS, et les règles associées (secrets, commit-avant-deploy, drift, smoke).
Asymétrie ./ship vs ./deploy
Deux points d'entrée existent à la racine du dépôt. Ils ne font pas la même chose et n'ont pas le même propriétaire.
./deploy |
./ship |
|
|---|---|---|
| Cible | Pré-production (ou runtime vaisseau-mère / PROD pour les sites fondateurs sans preprod) | Production |
| Propriétaire | IA / worker / cascade, de façon systématique | Alex uniquement, geste manuel |
| Git | Auto-commit du dirty + reste sur la branche preprod |
git checkout main → git pull → git merge preprod -X theirs → git push origin main, puis retour preprod (trap EXIT) |
| Garde-fou dirty | Auto-commit silencieux, sauf si sur main (refus dur) |
Bloquant : refus si working tree sale, sauf --allow-dirty |
| Vérification de dérive de schéma | Une seule cible preprod câblée ; tout autre tenant → skip explicite. Bypass --skip-drift disponible. |
Vérification du schéma du hub (bloquante) + DB prod tenant si cible connue. Aucun équivalent --skip-drift : la dérive hub est strictement bloquante. |
| Application automatique de la dérive | Le moteur de correction de dérive génère et applique en une seule transaction les DDL idempotentes manquantes (CREATE TABLE IF NOT EXISTS, ALTER TABLE ADD COLUMN IF NOT EXISTS). Actif par défaut. Désactivable ponctuellement. Appliqué avant tout build ou rechargement. |
— (dérive appliquée manuellement avant le ship) |
| Smoke post-déploiement | Vérification HTTP des tenants, enchaînée avec un smoke visuel non bloquant. Désactivable. | Vérification de l'environnement de processus (anti-faux-positif) puis smoke HTTP + contrôles de contenu JSON. |
| Migrations | — | Affiche les fichiers SQL en attente, non bloquant, non auto-appliqué — aucun runner de base de données branché. |
| Clôture | — | Propose la fermeture des chantiers de type test (post-ship). |
Doctrine :
./ship <tenant>= Production, jamais en autonome, exclusif Alex. L'IA ne déclenche jamais./ship../deploy <tenant>= toujours l'IA, systématiquement et sans demander — y compris sur le vaisseau-mère où./deployéquivaut à un rebuild live du runtime (pas de preprod dédiée).
┌─────────────┐ ./deploy <tenant> ┌──────────────┐
IA → │ branche │ ──────────────────→ │ preprod / │
│ preprod │ (auto, N fois) │ runtime live │
└─────────────┘ └──────────────┘
│
│ Alex review preprod, valide
▼
┌─────────────┐ ./ship <tenant> ┌──────────────┐
Alex → │ merge preprod│ ──────────────────→ │ PRODUCTION │
│ → main │ (manuel uniquement) │ │
└─────────────┘ └──────────────┘
Exception site public sans preprod : ./deploy est refusé pour ce tenant — le site public n'a pas de preprod. On le modifie uniquement via ./ship.
Exception tenants fondateurs sans preprod : pour certains sites fondateurs, ./deploy pointe directement la production. La frontière deploy = preprod ne tient pas pour ces cas. La seule opération qui reste manuelle est ./ship (fusion preprod→main + cérémonie complète).
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 dépôt ; exit si sale, sauf
--allow-dirty. - Trap EXIT
cleanup: retour sur la branchepreprodgaranti même en cas d'erreur ; tout échec de checkout est signalé bruyamment (pour éviter de rester coincé surmain). - Nettoyage des fichiers root-owned : suppression des artefacts laissés par d'anciens déploiements, qui sinon bloquent l'écriture.
- Vérification de dérive du hub (bloquante) : aucun bypass. Suivie de la vérification de dérive DB prod du tenant si la cible est connue.
- Fusion
preprod → main: checkout main, pull, mergepreprod -X theirs, push origin main. - Migrations : affichage des fichiers SQL en attente (voir section suivante). Non bloquant, non auto-appliqué.
- Audit des agents : vérification de l'état des agents actifs (non bloquant).
- Déploiement prod : appel du dispatcher de déploiement avec les paramètres du tenant.
- Smoke prod : vérification de l'environnement de processus avant le smoke HTTP + contrôles de contenu JSON.
- Post-ship close : propose la fermeture des chantiers de type
test(ignoré via--no-close).
Asymétrie
--skip-drift:./deployaccepte--skip-driftpour bypasser la vérification de dérive en preprod (cas de hotfix urgent)../shipn'a aucun équivalent — la dérive du hub y est strictement bloquante. On ne livre jamais en production sur un schéma divergent.
Migrations de base de données lors du ship
Le ship affiche les fichiers SQL en attente mais ne les applique jamais : aucun runner de base de données n'est branché. Le bloc est purement informatif et non bloquant — il imprime les commandes à exécuter manuellement.
Un mapping tenant → scope de migrations évite de mélanger les migrations du vaisseau-mère et celles des tenants :
| Tenant | Dossier de migrations ciblé |
|---|---|
| Vaisseau-mère / runtime OS | Scope mothership |
| Un tenant e-commerce (ex. tenant A) | Scope propre au tenant A |
| Un tenant vape (ex. tenant B) | Scope propre au tenant B |
| Sites fondateurs (client, alexandrecarette, codemyshop) | Scope propre à chaque site |
all |
Cas spécial : agrège toutes les cibles |
| autre | Fallback : scope = nom du tenant |
Cas spécial all : agrège l'ensemble des 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 SQL du dossier correspondant.
Routage de ./deploy et ./ship par tenant
Chaque 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 de santé du verrou de concurrence, puis script de déploiement dédié au runtime OS (--prod --all) |
Idem script dédié runtime OS (--prod --all) |
| Site public sans preprod | Refusé — pas de preprod | Dispatcher générique (--all) |
| Site fondateur synedre.com | Dispatcher générique → PROD direct | Dispatcher générique ⚠️ vérification d'environnement de processus peut bloquer si le tenant n'est pas déclaré dans le vérificateur |
| Site fondateur CodeMyShop | Dispatcher générique → PROD direct (plus d'ansible/preprod) ; smoke activé | Dispatcher générique |
| Demo CodeMyShop | Dispatcher générique → staging démo | — |
| Tenant e-commerce A (preprod) | Dispatcher générique (--target=preprod --all) ; smoke désactivé (preprod derrière basic auth) |
Dispatcher générique sans --target → prod |
| Tenant vape B | Dispatcher générique ; smoke activé | ⚠️ Tombe dans le cas générique fallback — probablement en erreur si aucun VPS prod n'est défini |
| Site fondateur client | 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 e-commerce A (preprod), tenant vape B, client. Smoke post-jobs sur l'ensemble. ⚠️ Le smoke all teste la démo CodeMyShop mais le job déploie la prod vitrine — la vitrine prod n'est pas vérifiée dans ce chemin. |
3 jobs en parallèle : runtime OS, site public sans preprod, CodeMyShop. Smoke sur les trois. |
--list / -l |
Affiche l'inventaire des VPS clients (voir section dédiée) | — |
| autre (inconnu) | Fallback vers le script d'ansible legacy (--preprod) |
Fallback vers le script d'ansible legacy (--prod) |
Convergence vers le dispatcher unique : depuis la standardisation du pattern de déploiement et la purge des scripts legacy, tous les tenants distants sauf le vaisseau-mère passent par le dispatcher générique, aussi bien pour
./deployque pour./ship. Le vaisseau-mère conserve son script dédié car le self-déploiement du runtime est architecturalement distinct : build docker local, sans transfert réseau vers un VPS externe.
⚠️ Angle mort smoke
all: lors d'un./deploy all, le déploiement cible la vitrine CodeMyShop prod, mais le smoke suivant vérifie la démo staging. La vitrine de production n'est pas contrôlée dans ce chemin — point à corriger dans la boucle de smoke.
Le dispatcher piloté par YAML
Le pipeline de déploiement standard — utilisé pour tous les tenants distants, à l'exception du déploiement du vaisseau-mère lui-même — est piloté par un fichier de configuration YAML propre à chaque tenant. Un script de déploiement principal orchestre l'ensemble de la séquence en lisant ce fichier, en le validant, puis en enchaînant les étapes dans un ordre déterministe.
Résolution du fichier de configuration
À l'invocation, le dispatcher localise le fichier YAML à appliquer selon deux règles :
- Lorsque la cible désigne le vaisseau-mère, le dispatcher tente de charger un fichier de configuration dédié. Ce fichier est documenté dans le schéma officiel, mais n'existe pas sur disque : le vaisseau-mère suit un pipeline de self-déploiement distinct, activé en amont. Le branchement correspondant dans le dispatcher est donc inerte en pratique.
- Pour tout autre tenant, le dispatcher charge le fichier
deploy.yamlsitué dans le répertoire du tenant. Si une cible nommée est précisée (option--target=<X>), le fichierdeploy.<X>.yamlest utilisé à la place.
Note : le nom
synedre(sans suffixe) identifie le tenant fondateur du site synedre.com ; il suit le chemin tenant standard et n'active pas le branchement vaisseau-mère.
Étapes du dispatcher
Le dispatcher exécute les étapes suivantes dans l'ordre :
- Analyse des arguments : l'option
--target=<X>(le signe=est obligatoire), le flag--all(obligatoire) et le flag optionnel--cleansont extraits et validés. - Résolution et validation YAML : un validateur Python lit le fichier YAML, vérifie sa conformité au schéma, et émet les variables de configuration sous forme de paires clé-valeur évaluées par le shell. Toute erreur de validation provoque l'arrêt immédiat du pipeline.
- Vérification des arguments standard : les flags
CLEAN_CACHEetHAS_ALLsont consolidés. - Bannière de démarrage : affichage du contexte et initialisation du chronomètre de déploiement.
- Vérification de dérive de schéma (drift check) : si la section
driftest présente dans le YAML, le moteur compare l'état réel de la base de données au schéma attendu et signale les écarts. - Application automatique de la dérive (drift auto-apply) : activé par défaut, ce mécanisme génère et applique en une seule transaction les instructions DDL idempotentes manquantes (
CREATE TABLE IF NOT EXISTS,ALTER TABLE ADD COLUMN IF NOT EXISTS). Le schéma est mis à jour avant tout build ou rechargement. Cette étape peut être désactivée ponctuellement via une variable d'environnement dédiée. - Lancement des hooks en arrière-plan : l'auditeur d'agents est démarré en tâche de fond.
- Génération des routes i18n (prébuild) : si la section
seed_i18nest présente, le dispatcher interroge les tables de catégories et de métadonnées localisées du tenant (via SSH ou conteneur) et écrit le fichier JSON des segments de route localisés consommé par le module de routing international au moment du build. Cette étape est non bloquante : si la base de données est inaccessible, le build se replie sur les valeurs codées en dur. - Build Nuxt : compilation de l'application front-end.
- Seed i18n : si la section est présente, injection des données de traduction.
- Source maps : si le hook correspondant est activé, envoi des source maps.
- Packaging : création d'une archive compressée du build dans le répertoire temporaire de la machine locale.
- Push Git en arrière-plan : si le hook est activé, poussée du dépôt en tâche de fond.
- Upload de l'archive : transfert sécurisé de l'archive vers le VPS client.
- Rechargement distant : selon la variante déclarée dans le YAML, le dispatcher exécute soit un rechargement gracieux via le gestionnaire de processus (
pm2), soit un redémarrage du conteneur applicatif (docker compose restart). - Installation des automates récurrents (cron) : si la section
cronest présente, le dispatcher installe les entrées planifiées dans le crontab de l'utilisateur SSH sur le VPS client (jamais sur le vaisseau-mère). Chaque bloc est marqué et idempotent : l'ancien bloc est retiré avant réécriture. Une liste vide déclenche la suppression du bloc. Doctrine : les automates récurrents s'exécutent au plus près de la base de données du tenant. - Attente des hooks en arrière-plan : synchronisation avec les tâches Git push et audit d'agents.
- Health check : boucle de vérification de disponibilité de l'URL cible jusqu'à expiration du délai configuré.
- Bannière de fin : affichage du bilan et du temps total écoulé.
Schéma du fichier de configuration YAML
Chaque fichier de configuration est validé par le validateur Python avant tout déploiement. Les sections reconnues sont les suivantes :
| Section | Obligatoire | Description |
|---|---|---|
name |
Oui | Identifiant du déploiement (minuscules, chiffres, tirets, underscores). |
build |
Oui | Chemin du client local (local_client requis) et environnement Node (node_env, défaut production). |
ssh |
Oui | Hôte cible requis ; utilisateur SSH (défaut ubuntu) et chemin de clé optionnels. |
remote |
Oui | Variante de déploiement : pm2 ou docker. Voir garde-fous ci-dessous. |
health |
Oui | URL de vérification requise ; délai maximal d'attente (défaut 45 secondes). |
drift |
Non | Déclaration des écarts de schéma à détecter et appliquer automatiquement. |
seed_i18n |
Non | Active la génération prébuild des routes i18n et l'injection des traductions. |
hooks |
Non | Hooks optionnels : audit d'agents, upload de source maps, push Git. |
cron |
Non | Liste d'automates récurrents à installer sur le VPS client. |
Garde-fous de cohérence par variante
Le validateur applique des règles de cohérence strictes selon la variante choisie :
- Variante
pm2: le nom de l'application de processus (pm2_app) est requis ; les champs propres à Docker (container,subdir) sont interdits. - Variante
docker: le nom du conteneur cible (container) est requis ; les champs propres à PM2 sont interdits.
Validation de la section cron
Chaque entrée de la section cron est validée individuellement :
key: identifiant en kebab-case, unique dans le fichier.schedule: expression crontab en exactement 5 champs.command: commande non vide à exécuter.comment: description libre (optionnelle).
Une section cron déclarée vide ([]) est transmise à l'installeur comme signal de nettoyage : le bloc correspondant est retiré du crontab du tenant.
Exemple de fichier de configuration
Le fichier ci-dessous illustre une configuration type pour un tenant déployé via le gestionnaire de processus PM2, avec push Git activé :
name: mon-tenant
build:
local_client: un composant interne
node_env: production
ssh:
host: <adresse du VPS client>
remote:
variant: pm2
dir: /var/www/un composant interne
pm2_app: mon-tenant-nuxt
pm2_remote_user: codemyshop
health:
url: https://mon-tenant.example.com
hooks:
git_push: mon-tenant
Rappel : l'hôte SSH (
host) doit être renseigné avec l'adresse ou le nom d'hôte réel du VPS client. Aucune adresse IP réelle ne doit être versionnée dans un fichier de configuration partagé publiquement.
Pattern : compilation sur le vaisseau-mère, déploiement vers le VPS
L'invariant central pour les déploiements distants est le suivant : le VPS client ne compile jamais. Le bundle Nuxt est entièrement produit sur le vaisseau-mère, compressé, transféré, extrait sur place, puis le processus applicatif est rechargé. La bannière de départ du pipeline affiche explicitement ce mode de fonctionnement.
Déroulement en huit étapes
┌─────────────────── VAISSEAU-MÈRE (compilation) ───────────────────┐
│ 1. Installation des dépendances (delta, mode hors-ligne préféré) │
│ 2. Exécution des tests smoke d'invariants d'URL │
│ 3. Compilation Nuxt du client cible → .output/ │
│ 4. Archivage compressé (pigz si disponible, sinon gzip) │
└───────────────────────────┬───────────────────────────────────────┘
│ transfert sécurisé (SCP)
▼
┌─────────────────── VPS CLIENT ────────────────────────────────────┐
│ 5. Rotation .output → .output_old (rollback prêt) │
│ 6. Extraction de l'archive compressée │
│ 7. Rechargement du processus (graceful reload ou restart) │
│ 8. Si en ligne → suppression de .output_old │
│ Sinon → affichage de la commande de restauration + stop │
└───────────────────────────┬───────────────────────────────────────┘
│
▼ sondage HTTP 200 (délai max configurable)
vérification de santé
Détail des fonctions du pipeline
- Compilation Nuxt : purge du répertoire de sortie (et du cache incrémental si l'option
--cleanest passée), installation des dépendances en mode hors-ligne, puis test smoke bloquant sur les invariants de forme des URL produit avant toutnuxi build. Si le build incrémental échoue, une tentative avec cache purgé est automatiquement relancée. - Archivage : le répertoire
.outputdu client est empaqueté dans une archive compressée.pigzest utilisé si disponible sur le vaisseau-mère, avec repli automatique surgzip. - Transfert : l'archive est copiée sur le VPS via SCP, puis supprimée localement.
- Redémarrage Docker : connexion SSH au VPS, rotation
.output → .output_old, extraction de l'archive dans le répertoire cible, suppression d'un module CommonJS superflu identifié lors d'une cicatrice antérieure (tree-shake CJS), redémarrage du conteneur applicatif, vérification que le conteneur est bien en étatrunning— sinon rollback. - Rechargement PM2 graceful :
pm2 reloadavec mise à jour des variables d'environnement — préserve les connexions en cours. Gère optionnellement un utilisateur PM2 dédié sur le VPS. Un lien symbolique optionnel vers le dossier des uploads du back-office peut être (re)créé pour éviter les erreurs de fichiers statiques introuvables. - Redémarrage PM2 hard : suppression puis recréation complète du processus PM2 (premier déploiement ou configuration explicite via paramètre
pm2_start), suivi d'une sauvegarde de la liste des processus. - Boucle de vérification de santé : sondage HTTP répété jusqu'à obtenir un code
200ou expiration du délai maximum (45 secondes par défaut). - Bannières de départ et de fin : encadrement visuel du pipeline avec décomposition des durées par phase.
- Installateur de tâche planifiée : installe la tâche cron par client sur le VPS distant (voir §3).
Comportement du rollback
.output_old est conservé pendant toute la durée du rechargement. Il est purgé uniquement si le processus redémarre avec succès. En cas d'échec, le script affiche uniquement la commande de restauration suggérée (mv .output_old .output) et se termine avec un code d'erreur — la restauration reste manuelle, le déplacement n'est jamais exécuté automatiquement. Pour forcer la conservation de .output_old même en cas de succès, la variable SHIP_KEEP_ROLLBACK=1 peut être positionnée.
Smoke post-déploiement : trois couches de validation
Le script de vérification post-déploiement comprend deux sous-niveaux de vérification formels, complétés d'un troisième niveau visuel non-bloquant.
-
Vérifications HTTP : sondage de chaque URL déclarée pour le client ; tout code
5xxest bloquant, un4xxgénère un avertissement non-bloquant. Les clients couverts incluent le cockpit principal, la vitrine démo, plusieurs tenants de production et le site institutionnel. Une boucle interne couvre six clients en mode all ; deux autres sont vérifiés individuellement depuis le pipeline de livraison. -
Vérifications de contenu / JSON : valide la forme du JSON retourné par l'API de navigation principale. Cette couche a été introduite après un incident où une page renvoyait HTTP
200pendant deux jours avec un JSON portant{ error: true }, rendant la navigation entièrement vide et le pied de page affiché en clés brutes. Sans ce niveau, les rendus SSR pilotés par la base de données pouvaient échouer silencieusement. Les clients disposant d'une navigation en base de données sont couverts ; les clients sans navigation globale (cockpit headless, vitrine sans navigation configurée) sont volontairement exclus. - Vérification visuelle automatisée (non-bloquante) : capture d'écran Playwright suivie d'un verdict multimodal. Le module est ignoré proprement si aucun fichier de configuration de vérifications visuelles n'existe pour le client (zéro latence ajoutée). Actif par défaut, désactivable via variable d'environnement.
Anti-faux-positif : vérification des variables d'environnement PM2
Un code HTTP 200 ne suffit pas à valider un déploiement PM2. Un incident a démontré qu'un client pouvait être redéployé sans aucune variable de connexion à la base de données : toutes les routes SSR pilotées par les données renvoyaient 500, mais la page servait un squelette vide syntaxiquement valide — le smoke HTTP retournait donc 200 pendant deux jours.
C'est pourquoi le script de livraison lance un vérificateur d'environnement PM2 avant la vérification smoke : il se connecte en SSH au VPS distant et inspecte la présence des variables critiques dans l'environnement du processus PM2 actif.
| Code retour | Signification | Comportement |
|---|---|---|
0 |
Toutes les variables critiques sont présentes | Pipeline continue |
1 |
Variables manquantes | Bloquant — arrêt du pipeline |
2 |
Connexion SSH ou processus PM2 inaccessible | Bloquant — arrêt du pipeline |
Les cibles non-PM2 (vaisseau-mère headless, environnements Docker purs) retournent automatiquement 0 et ne sont pas inspectées.
Vérification de cohérence du lockfile avant compilation
Avant tout déploiement du cockpit principal, le pipeline exécute un contrôle de cohérence du lockfile détectant deux décalages bloquants :
- Version de minifieur CSS inférieure au seuil requis dans le lockfile (dépendances PostCSS transitives manquantes → build cassé silencieusement).
- Version de Nuxt inférieure au seuil requis dans le manifeste de dépendances (risque de décalage avec l'outil de bundling).
Un avertissement non-bloquant est émis si des surcharges de version de Vite sont détectées dans la configuration racine. En cas d'erreur bloquante, le script se termine avec un code 1 et un message correctif explicite — le déploiement est annulé. La correction suggérée consiste à régénérer le lockfile depuis zéro.
Déploiement du cockpit principal : auto-déploiement local
Le cockpit principal ne passe pas par le pipeline de déploiement distant décrit ci-dessus. C'est un auto-déploiement local : le vaisseau-mère compile à l'intérieur de son propre conteneur applicatif. Un script dédié orchestre l'ensemble en mode --prod --all.
Spécificités par rapport aux déploiements distants
- Absence d'environnement de pré-production depuis mi-2026 :
./deploysur le cockpit principal est un push direct en production live, sans la cérémonie de livraison de./ship. - Transfert local par copie Docker : une archive source minimale (code applicatif + manifestes de dépendances, sans
.nuxt,.outputninode_modules) est copiée dans le conteneur viadocker cp— aucun transfert réseau. - Préservation du cache de build incrémental : avant l'extraction de l'archive, le cache
.nuxtexistant dans le conteneur est mis de côté puis restauré après extraction. Ce mécanisme est le principal levier de performance : le build reste incrémental et non un cold build complet. - Compilation à l'intérieur du conteneur : le build Nuxt s'exécute dans le conteneur actif via
docker exec. L'ancien serveur reste en ligne pendant toute la durée de la compilation. Aucun gestionnaire de processus PM2 n'est utilisé dans ce pipeline : le conteneur démarre directement le serveur Node. - Séquence de rotation : arrêt gracieux du conteneur → copie du nouveau
.outputvers l'hôte → recréation forcée du conteneur (relit les fichiers d'environnement). Cette étape a été introduite après un incident où les variables d'environnement n'étaient pas rechargées lors d'un simplerestart. - Base de code autonome : le cockpit principal n'étend plus le socle commun — l'archive transférée est allégée d'environ 16 MB par rapport à l'ancienne configuration.
- Optimisation npm par empreinte : une empreinte SHA-256 du fichier de verrou des dépendances est conservée dans le conteneur. Si le lockfile n'a pas changé depuis le dernier déploiement, l'étape
npm installest intégralement sautée. - Vérification de santé par UUID de build : la vérification ne se limite pas à un HTTP
200. L'UUID du build attendu (lu depuis le manifeste de build côté hôte) est comparé à celui retourné par le point de terminaison/api/healthdu conteneur. Cela garantit que c'est bien le nouveau build qui sert les requêtes, et non l'ancien maintenu par un rechargement partiel. - Variante accélérée : une option opt-in permet de déporter la compilation sur l'hôte plutôt que dans le conteneur, avec un swap atomique du répertoire de sortie (~35 % de gain de temps).
- Cérémonie de livraison
./ship: ajoute la fusion de branche, le push, les audits d'agents, le smoke de production et la clôture de chantier, mais appelle en final le même script de déploiement local.
Note : contrairement aux VPS clients, le conteneur du cockpit principal ne passe jamais par la phase de transfert SCP ni par PM2. La compilation et le service applicatif sont entièrement conteneurisés sur le même hôte physique.
Inventaire de la topologie — source unique de vérité
La topologie complète de l'infrastructure (liste des environnements, accès SSH, bases de données associées) est stockée dans une seule table de référence, hébergée dans la base de données centrale du vaisseau-mère. Cette table est la source de vérité unique : aucun fichier d'architecture, aucun script, aucun agent ne doit dupliquer ou coder en dur ces informations — tout autre document peut être obsolète.
La commande de listage de l'inventaire interroge cette table et affiche un tableau aligné en console. La requête filtre les entrées actives, ordonne les résultats par criticité décroissante, et formate les colonnes pour une lecture rapide.
-- Exemple illustratif (colonnes réelles, valeurs anonymisées)
SELECT
rpad(identifiant_tenant, 14) AS tenant,
rpad(environnement, 11) AS env,
rpad(coalesce(domaine, '-'), 40) AS domaine,
CASE WHEN stack_nuxt THEN 'nuxt' ELSE 'ps' END AS stack
FROM inventaire_topologie
WHERE actif = 1
ORDER BY criticite DESC, identifiant_tenant, environnement;
Colonnes notables de la table d'inventaire
| Colonne | Rôle |
|---|---|
| Identifiant tenant | Clé logique primaire identifiant l'environnement client |
| Environnement | production | staging | infra | legacy | audit |
| Accès SSH | Adresse du VPS client, utilisateur et chemin de clé SSH |
| Base de données | Références vers le container DB, le nom de base et l'identifiant de la variable d'environnement contenant le mot de passe (jamais la valeur) |
| Runtime web | Container applicatif, présence PrestaShop, présence Nuxt |
| Criticité business | Niveau de criticité, revenu mensuel récurrent, offre souscrite |
| Ship automatique autorisé | Booléen autorisant un déploiement automatique — à confirmer avant usage |
| Codename de déploiement | Identifiant utilisé pour composer la commande exacte de déploiement |
| Codename client | Clé étrangère vers la table de fédération client (référentiel centralisé) |
État de l'inventaire actif
L'inventaire comprend plusieurs catégories d'entrées actives :
- Tenants en production active : environnements critiques hébergeant des boutiques ou sites en service, sur un ou plusieurs VPS clients.
- Environnements staging : copies de recette associées aux tenants principaux.
- Infra vaisseau-mère : l'entrée désignant le VPS central du vaisseau-mère lui-même.
- Productions orphelines : certains tenants portent
purpose='production'tout en étant en sommeil — il s'agit de projets secondaires sans maintenance active. Ils ne doivent pas être agrégés sous l'étiquette « legacy ». - Double entrée hétérogène : un tenant peut apparaître en deux lignes distinctes (ex. une ligne production Nuxt et une ligne legacy sans Nuxt) reflétant deux générations techniques coexistantes. Ne pas les fusionner.
- Entrée décommissionnée : un tenant ayant fait l'objet d'une mission ponctuelle terminée a été passé
actif=0; son dossier opérationnel a été retiré, mais son historique est conservé dans le référentiel client centralisé.
Doctrine opérationnelle : avant chaque déploiement (
./deploy) ou mise en production (./ship), consulter le cadran de gestion des tenants pour y copier la commande exacte associée au codename cible. Ne jamais recomposer la commande de mémoire.
Doctrine de gestion des secrets — cinq niveaux
Chaque secret possède un seul fichier canonique. La présence d'une même clé dans deux fichiers distincts est traitée comme un bug, non comme une redondance de sécurité.
Les cinq niveaux de fichiers de secrets
| # | Fichier | Périmètre | Suivi git |
|---|---|---|---|
| 1 | Fichier d'environnement du tenant | Un seul tenant — lu par son container applicatif et la configuration de déploiement | Non |
| 2 | Fichier d'environnement du cœur vaisseau-mère | Services centraux du vaisseau-mère | Non |
| 3 | Fichier d'environnement hôte | Scripts hôte : tâches planifiées, automates, accès SSH aux VPS clients | Non |
| 4 | Fichier d'environnement partagé | Cross-projet : clés API des fournisseurs IA, clé de chiffrement API, configuration SMTP maître | Non (hors dépôt) |
| 5 | Fichiers d'exemple (*.env.example) |
Templates publics open-source — ne contiennent aucune valeur réelle | Oui |
Ordre de chargement et règle de précédence
Au démarrage du service central, les fichiers d'environnement sont chargés dans l'ordre suivant : fichier partagé → fichier cœur → fichier hôte. En cas de clé présente dans le fichier cœur et dans le fichier hôte, le fichier hôte prend la priorité. Il est donc interdit de déposer la même clé dans ces deux niveaux.
Règle anti-fuite P0
Aucun secret en clair ne doit apparaître dans un fichier suivi par git. Lorsqu'un fichier versionné doit référencer un secret, il cite uniquement le nom de la variable d'environnement et le fichier .env* qui la porte — jamais sa valeur. Par exemple, la configuration de déploiement d'un tenant référence le nom de la variable contenant le mot de passe de base de données ; la table d'inventaire topologie stocke de même le nom de la variable, pas le mot de passe lui-même.
Note : la vue résumée en trois niveaux (infra / cœur / tenants) constitue l'entrée rapide dans la documentation principale. La doctrine complète à cinq niveaux — qui ajoute le fichier partagé cross-projet et les templates publics — est la référence faisant foi en cas de divergence.
Règle de commit avant déploiement
Une modification appliquée localement mais jamais commitée ne survit pas à un déploiement : le mécanisme de synchronisation du dépôt restaure la version commitée et écrase tout changement flottant. Un incident passé (correction d'URL en production restée non commitée, écrasée lors d'un déploiement suivant) a causé plusieurs heures de production cassée. Cette règle est donc classée P0.
Comportement du garde-fou de propreté
Un script de vérification de l'état du dépôt est exécuté avant chaque opération de déploiement. Il retourne trois états : propre, modifié (dirty), ou hors-dépôt. La vérification est scopée par cible : un fichier modifié côté vaisseau-mère ne bloque pas un déploiement tenant, et inversement.
-
Lors d'un déploiement preprod (
./deploy) : si l'état est dirty, un commit automatique silencieux est généré (chore(auto): pre-deploy snapshot <horodatage ISO>) et poussé en arrière-plan. Exception : si la branche courante estmain, le déploiement est refusé — aucun commit automatique sur la branche principale. -
Lors d'une mise en production (
./ship) : si l'état est dirty, la commande est bloquante et retourne une erreur explicite invitant à commiter d'abord. Un paramètre de contournement existe pour les corrections d'urgence volontaires uniquement.
Un hook de fin de session bloque également la clôture de session si le dépôt n'est pas propre. La règle de workflow est la suivante : aucun travail terminé ne reste uncommitted — l'agent commente et commite, l'opérateur humain ne manipule jamais directement les commandes git de base.
Annexe — fichiers de référence
| Composant | Rôle |
|---|---|
| Entrypoint de déploiement preprod | Point d'entrée racine pour les déploiements de recette et le routage vers les tenants |
| Entrypoint de mise en production | Point d'entrée racine pour les mises en production — opérateur humain uniquement — avec cérémonie complète de validation |
| Dispatcher de déploiement | Lit la configuration de déploiement au format YAML et orchestre les déploiements distants pour tous les tenants (hors vaisseau-mère) |
| Bibliothèque de helpers de déploiement | Fonctions réutilisables : build, packaging, upload, rechargement, vérification de santé, bannière, tâches planifiées |
| Parseur de configuration de déploiement | Analyse et valide le fichier de configuration YAML et expose les variables de déploiement (sections : build, SSH, dérive, initialisation i18n, remote, santé, hooks, tâches planifiées) |
| Script de self-déploiement vaisseau-mère | Déploie le service central du vaisseau-mère via build dans le container suivi d'un swap atomique stop/copie/start |
| Variante de déploiement accélérée | Effectue le build sur l'hôte directement puis réalise le swap atomique — plus rapide pour les itérations courtes |
| Garde-fou de propreté git | Vérifie l'état du dépôt avant déploiement ou mise en production (propre / modifié / hors-dépôt) |
| Garde-fou de cohérence des dépendances | Vérifie la cohérence des versions de lockfile avant déploiement du vaisseau-mère (versions minimales requises pour les outils CSS et le framework) |
| Script de smoke test HTTP | Vérifie la disponibilité et le contenu des endpoints après déploiement — huit branches de cas couverts ; mode all couvre l'ensemble des tenants actifs |
| Vérificateur d'environnement de processus | Contrôle la configuration de l'environnement du gestionnaire de processus avant les smoke tests en production — plusieurs tenants couverts ; certains environnements sans gestionnaire de processus sont exclus par conception |
| Schéma de configuration de déploiement | Spécification du format YAML de déploiement avec quatre exemples canoniques |
| Runbook d'exploitation VPS | Procédures opérationnelles SSH, gestionnaire de processus, Docker, renouvellement de certificats |
| Doctrine des secrets | Référence complète des cinq niveaux de gestion des secrets |
| Table d'inventaire topologie | Source unique de vérité pour la topologie de l'infrastructure (voir §6) |
Avertissement : le document de workflow historique est partiellement obsolète. Il décrit encore un environnement de preprod mothership (port dédié, sous-domaine de recette) et un script de ship séparé comme pipeline actif, alors que la preprod vaisseau-mère a été démantelée et que le déploiement passe désormais par le script de self-déploiement décrit ci-dessus. En cas de conflit, le code fait foi, pas la documentation narrative.