Session 5 — PerfShop.io — Plateforme de suivi des déploiements¶
| Durée | ~8 heures |
| Conversations | 2 |
| Objectif initial | Créer perfshop.io — site vitrine + admin + suivi déploiements |
| Objectif final | Plateforme complète avec UUID auto, hostname/IP publique, statut instances temps réel, gestion comptes admin |
🎯 Réalisations¶
1. Infrastructure perfshop.io · ~1h · ⭐⭐¶
Backend Express.js + PostgreSQL 16 + Nginx reverse proxy (SSL Synology) + sessions admin sécurisées (bcrypt + express-session).
2. Deploy-ping — comptage déploiements actifs · ~1h30 · ⭐⭐⭐¶
Service one-shot perfshop-deploy-ping lancé au docker compose up :
- Envoie : version, compose file, hostname hôte, hostname Docker, IP publique, UUID instance, OS/arch
- Géolocalisation côté serveur → pays, ville, FAI
- Tableau admin avec drapeaux, version, hostname
Problème clé : Variables vides dans l'entrypoint inline Docker Compose (double quotes → évaluées par le shell hôte).
Solution : Script dédié perfshop-deploy-ping.sh monté en volume.
3. UUID instance — génération automatique stable · ~45 min · ⭐⭐⭐¶
- Volume nommé → bind mount
./instance(résout permissions Synology) - UUID basé sur hostname via
md5sum(reproductible si volume perdu) /etc/hostnamemonté en read-only → hostname hôte réel- Fallback
$COMPUTERNAMEpour Windows ./instance/ajouté au.gitignore
4. Statut instances — monitoring temps réel · ~1h · ⭐⭐⭐¶
Service permanent perfshop-status-checker (60s) — carte "Ma production" + tableau autres instances + stats globales. Corbeille → suppression dans service_status ET deployments.
Problème clé : Instances toujours "hors ligne" → 304 Not Modified → ETag automatique malgré etag: false.
Solution : Middleware Cache-Control: no-store + removeHeader('ETag') global sur /api/.
5. Corrections backend · ~45 min · ⭐⭐⭐¶
public_ipextraite après utilisation → corrigéJOIN DISTINCT ON→ JOIN LATERAL pour dernier déploiement par instancehostname_dockeroublié dans 2 SELECT → ajouté
6. Gestion comptes admin — profil + protection fondateur · ~1h · ⭐⭐¶
- Colonnes
first_name,last_name,phonesur la tableadmin - Routes
GET /accounts/me+PATCH /accounts/me - Badge 🔒 Fondateur pour
id=1, suppression bloquée côté backend (HTTP 403) initAdminAccount()au démarrage si table vide
7. Redesign statut instances — pastilles colorées · ~30 min · ⭐¶
Remplacement emojis ✅/❌/⬛ par pastilles CSS avec glow vert/rouge. Texte UP/DOWN centré dans les cartes.
8. Docker Compose — compatibilité Windows + Synology · ~1h30 · ⭐⭐⭐⭐¶
docker-compose.build.yml mis à jour, bind mount ./instance, /etc/hostname en volume, fallback $COMPUTERNAME. README.md mis à jour.
🎢 Chronologie Détaillée¶
| Phase | Ressenti | Événement clé |
|---|---|---|
| Phase 1 (~2h) | 🟢 Clair | Script ping + UUID stable + bind mount |
| Phase 2 (~1h30) | 🟡 Non évident | Variables vides entrypoint → script dédié + bug ordre SQL |
| Phase 3 (~1h30) | 🔴 Long | 304 Not Modified → ETag → middleware no-store |
| Phase 4 (~1h) | 🟢 Méthodique | Profil admin + protection id=1 + seed auto |
| Phase 5 (~30 min) | 🟢 Rapide | Redesign pastilles + centrage |
🧠 Moments Clés d'Apprentissage¶
- Entrypoint Docker Compose inline = piège de quotes — Double quotes → variables évaluées par le shell hôte. Toujours déporter dans un script
.sh. /etc/hostname≠$HOSTNAMEdans Docker —$HOSTNAME= hostname container. Monter/etc/hostnamede l'hôte en bind mount.- ETag + Cache-Control sur les APIs dynamiques — helmet regénère des ETags malgré
etag: false. Middleware globalno-store+removeHeader('ETag'). - Ordre d'extraction des variables Node — Toujours
const { public_ip } = req.bodyavant d'utiliserpublic_ip. - Table vide après rebuild —
docker compose up --buildne rejoue pas les seeds.initAdminAccount()au démarrage. --remove-orphanssupprime les services retirés — Vérifier le compose avant d'exécuter.
🎯 Difficultés Rencontrées¶
| Sévérité | Problème | Temps perdu | Solution |
|---|---|---|---|
| 🔴 ⭐⭐⭐⭐⭐ | Instance "hors ligne" malgré rapports envoyés (304 + ETag + rebuild) | ~1h | Middleware no-store global |
| 🟡 ⭐⭐⭐⭐ | Variables vides dans deploy-ping | ~45 min | Script dédié monté en volume |
| 🟡 ⭐⭐⭐⭐ | Table admin vide après rebuild → 404 |
~30 min | initAdminAccount() au démarrage |
📊 Métriques de la Session¶
| Métrique | Valeur |
|---|---|
| Durée totale | ~8 heures |
| Conversations Claude | 2 |
| Fichiers backend créés/modifiés | 5 |
| Scripts Docker créés | 2 |
| Fichiers Docker Compose modifiés | 2 |
| Bugs corrigés | 6 |
| Lignes de code | ~600 (JS + Shell + YAML) |
✅ Ce Qui a Bien Fonctionné¶
Architecture deploy-ping¶
- Service one-shot propre : une responsabilité unique, exit après l'envoi
- Géolocalisation côté serveur → aucune dépendance client
- UUID basé sur
md5sum hostname: reproductible même après perte de volume
Diagnostic méthodique ETag¶
curl -vsur les endpoints → identification des headers réels- Isolation du problème par couches (Express → helmet → nginx)
- Solution générique : middleware global plutôt que correctif ponctuel
Protection fondateur robuste¶
- Blocage côté backend (HTTP 403), pas seulement côté UI
- Seed automatique au démarrage → aucune intervention manuelle post-deploy
🎓 Conclusion¶
Cette session de ~8 heures en vibe coding a permis de :
✅ Construire perfshop.io de zéro — backend Express.js, PostgreSQL, Nginx, sessions sécurisées, frontend admin complet
✅ Implémenter le deploy-ping — chaque docker compose up envoie automatiquement version, hostname, IP, géolocalisation
✅ Résoudre l'UUID stable — bind mount + md5sum sur hostname, reproductible même si le volume est perdu
✅ Monitoring temps réel — statut instances avec pastilles colorées, suppression propre, cache ETag neutralisé
✅ Protéger le compte fondateur — id=1 protégé côté backend HTTP 403, seed automatique au démarrage
Ratio temps productif / temps total : ~75% Objectifs atteints : 100% (plateforme déployée et opérationnelle sur Synology)
Le vibe coding a particulièrement bien fonctionné sur l'architecture deploy-ping : un besoin exprimé simplement ("je veux savoir quand quelqu'un déploie") s'est transformé en un service one-shot propre, avec géolocalisation et UUID stable, en moins de 2 heures.
Le moment le plus laborieux reste le bug ETag/304 : une instance qui rapporte correctement mais apparaît toujours "hors ligne" est un symptôme trompeur — le problème venait d'une couche HTTP invisible (cache helmet) et non de la logique métier. Le diagnostic a nécessité d'inspecter les headers bruts avec curl -v pour identifier la vraie cause.
Leçon de cette session : dans un projet Node.js/Express, toujours tester les APIs avec curl -v dès qu'un comportement de cache est suspect — les middlewares comme helmet peuvent réintroduire silencieusement des comportements qu'on croyait désactivés.
🤖 Erreurs imputables à Claude AI — Session 5¶
❌ Erreur S5-1 — Variables Docker Compose inline sans anticiper l'évaluation par le shell hôte¶
Ce que j'ai proposé : Un entrypoint inline dans docker-compose.yml avec des variables d'environnement entre double quotes pour le service perfshop-deploy-ping.
Ce qui s'est passé : Les variables ($HOSTNAME, $COMPUTERNAME) étaient évaluées par le shell hôte au moment du docker compose up, pas dans le container. Résultat : variables vides dans le ping.
Pourquoi c'était une erreur : Le comportement de substitution des variables dans les entrypoints inline Docker Compose est connu — les doubles quotes ne protègent pas de l'expansion côté hôte. J'aurais dû proposer d'emblée un script .sh dédié monté en volume, qui est la pratique standard pour tout entrypoint non trivial.
Temps perdu : ~45 min | Sévérité : ⭐⭐⭐⭐
❌ Erreur S5-2 — ETag non neutralisé malgré etag: false sur l'app Express¶
Ce que j'ai proposé : Désactiver le cache avec app.set('etag', false) sur l'application Express pour éviter les 304 sur les endpoints de statut.
Ce qui s'est passé : Les instances restaient "hors ligne" malgré des rapports envoyés correctement. helmet réinjectait des ETags au niveau middleware, après le app.set. Le 304 Not Modified renvoyait l'ancien statut au lieu des données fraîches.
Pourquoi c'était une erreur : app.set('etag', false) contrôle uniquement le générateur ETag natif d'Express — il ne désactive pas les ETags ajoutés par des middlewares tiers. J'aurais dû proposer directement un middleware global Cache-Control: no-store + removeHeader('ETag') sur toutes les routes /api/, ce qui est la seule garantie réelle.
Temps perdu : ~1h | Sévérité : ⭐⭐⭐⭐⭐
❌ Erreur S5-3 — public_ip utilisée avant extraction du body¶
Ce que j'ai proposé : Du code qui référençait public_ip dans la logique de traitement avant la ligne const { public_ip } = req.body.
Ce qui s'est passé : public_ip était undefined dans les enregistrements en base — bug silencieux, aucune erreur levée, juste une valeur manquante.
Pourquoi c'était une erreur : Erreur d'ordre de déclaration classique, que j'aurais dû éviter en structurant systématiquement le handler : d'abord toutes les extractions du body, puis la logique.
Temps perdu : ~15 min | Sévérité : ⭐⭐⭐
❌ Erreur S5-4 — JOIN DISTINCT ON au lieu de JOIN LATERAL pour le dernier déploiement¶
Ce que j'ai proposé : Une requête avec DISTINCT ON pour récupérer le dernier déploiement par instance.
Ce qui s'est passé : Résultats incorrects — plusieurs lignes par instance ou mauvaise ligne sélectionnée selon l'ordre de tri.
Pourquoi c'était une erreur : DISTINCT ON en PostgreSQL nécessite que l'ORDER BY commence par les mêmes colonnes que le DISTINCT ON, ce qui entre en conflit avec un tri secondaire. JOIN LATERAL avec une sous-requête ORDER BY … LIMIT 1 est la forme correcte et lisible pour "le dernier enregistrement par groupe".
Temps perdu : ~20 min | Sévérité : ⭐⭐⭐
❌ Erreur S5-5 — Table admin vide après rebuild non anticipée¶
Ce que j'ai proposé : Un setup initial sans mécanisme de seed automatique pour le compte admin.
Ce qui s'est passé : Après un docker compose up --build, la table admin était vide → route /accounts/me retournait 404 → l'interface admin était inutilisable sans intervention manuelle.
Pourquoi c'était une erreur : Tout projet avec une table d'administration doit avoir un seed de sécurité au démarrage. J'aurais dû inclure initAdminAccount() dès la création de la route, pas comme correctif après incident.
Temps perdu : ~30 min | Sévérité : ⭐⭐⭐⭐
📊 Récapitulatif Session 5¶
| # | Erreur | Temps perdu | Sévérité |
|---|---|---|---|
| S5-1 | Variables Docker Compose inline évaluées par le shell hôte | ~45 min | ⭐⭐⭐⭐ |
| S5-2 | ETag réinjecté par helmet malgré etag: false |
~1h | ⭐⭐⭐⭐⭐ |
| S5-3 | public_ip utilisée avant extraction du body |
~15 min | ⭐⭐⭐ |
| S5-4 | DISTINCT ON au lieu de JOIN LATERAL |
~20 min | ⭐⭐⭐ |
| S5-5 | Table admin vide après rebuild non anticipée |
~30 min | ⭐⭐⭐⭐ |
| TOTAL | ~2h50 |
Pattern : Hypothèses implicites sur le comportement des outils (etag: false suffisant, variables inline évaluées dans le container, seed inutile) — le même pattern que les sessions précédentes, appliqué cette fois à l'écosystème Node.js/Docker plutôt qu'à Java/Spring.