Deux clones du template partagent le projet Compose template : le second docker compose up s'attache à la base du premier et joue ses migrations dessus #62
Closed
opened 2026-09-12 15:53:03 +00:00 by nicolas-fryder
·
3 comments
No Branch/Tag specified
main
dev
@eqs/tooling@0.24.3
@eqs/testing@0.24.3
@eqs/telemetry@0.24.3
@eqs/platform-next@0.24.3
@eqs/platform-nest@0.24.3
@eqs/http@0.24.3
@eqs/errors@0.24.3
@eqs/config@0.24.3
@eqs/cli@0.24.3
@eqs/auth@0.24.3
@eqs/tooling@0.24.2
@eqs/testing@0.24.2
@eqs/telemetry@0.24.2
@eqs/platform-next@0.24.2
@eqs/platform-nest@0.24.2
@eqs/http@0.24.2
@eqs/errors@0.24.2
@eqs/config@0.24.2
@eqs/cli@0.24.2
@eqs/auth@0.24.2
@eqs/tooling@0.24.1
@eqs/testing@0.24.1
@eqs/telemetry@0.24.1
@eqs/platform-next@0.24.1
@eqs/platform-nest@0.24.1
@eqs/http@0.24.1
@eqs/errors@0.24.1
@eqs/config@0.24.1
@eqs/cli@0.24.1
@eqs/auth@0.24.1
@eqs/tooling@0.24.0
@eqs/testing@0.24.0
@eqs/telemetry@0.24.0
@eqs/platform-next@0.24.0
@eqs/platform-nest@0.24.0
@eqs/http@0.24.0
@eqs/errors@0.24.0
@eqs/config@0.24.0
@eqs/cli@0.24.0
@eqs/auth@0.24.0
@eqs/tooling@0.23.0
@eqs/testing@0.23.0
@eqs/telemetry@0.23.0
@eqs/platform-next@0.23.0
@eqs/platform-nest@0.23.0
@eqs/http@0.23.0
@eqs/errors@0.23.0
@eqs/config@0.23.0
@eqs/cli@0.23.0
@eqs/auth@0.23.0
@eqs/tooling@0.22.0
@eqs/testing@0.22.0
@eqs/telemetry@0.22.0
@eqs/platform-next@0.22.0
@eqs/platform-nest@0.22.0
@eqs/http@0.22.0
@eqs/errors@0.22.0
@eqs/config@0.22.0
@eqs/cli@0.22.0
@eqs/auth@0.22.0
@eqs/tooling@0.21.0
@eqs/testing@0.21.0
@eqs/telemetry@0.21.0
@eqs/platform-next@0.21.0
@eqs/platform-nest@0.21.0
@eqs/http@0.21.0
@eqs/errors@0.21.0
@eqs/config@0.21.0
@eqs/cli@0.21.0
@eqs/auth@0.21.0
@eqs/tooling@0.20.0
@eqs/testing@0.20.0
@eqs/telemetry@0.20.0
@eqs/platform-next@0.20.0
@eqs/platform-nest@0.20.0
@eqs/http@0.20.0
@eqs/errors@0.20.0
@eqs/config@0.20.0
@eqs/cli@0.20.0
@eqs/auth@0.20.0
@eqs/tooling@0.19.0
@eqs/testing@0.19.0
@eqs/telemetry@0.19.0
@eqs/platform-next@0.19.0
@eqs/platform-nest@0.19.0
@eqs/http@0.19.0
@eqs/errors@0.19.0
@eqs/config@0.19.0
@eqs/cli@0.19.0
@eqs/auth@0.19.0
@eqs/tooling@0.17.1
@eqs/testing@0.17.1
@eqs/telemetry@0.17.1
@eqs/platform-next@0.17.1
@eqs/platform-nest@0.17.1
@eqs/http@0.17.1
@eqs/errors@0.17.1
@eqs/config@0.17.1
@eqs/cli@0.17.1
@eqs/auth@0.17.1
@eqs/tooling@0.17.0
@eqs/testing@0.17.0
@eqs/telemetry@0.17.0
@eqs/platform-next@0.17.0
@eqs/platform-nest@0.17.0
@eqs/http@0.17.0
@eqs/errors@0.17.0
@eqs/config@0.17.0
@eqs/cli@0.17.0
@eqs/auth@0.17.0
@eqs/tooling@0.16.0
@eqs/testing@0.16.0
@eqs/telemetry@0.16.0
@eqs/platform-next@0.16.0
@eqs/platform-nest@0.16.0
@eqs/http@0.16.0
@eqs/errors@0.16.0
@eqs/config@0.16.0
@eqs/cli@0.16.0
@eqs/auth@0.16.0
@eqs/tooling@0.15.0
@eqs/testing@0.15.0
@eqs/telemetry@0.15.0
@eqs/platform-next@0.15.0
@eqs/platform-nest@0.15.0
@eqs/http@0.15.0
@eqs/errors@0.15.0
@eqs/config@0.15.0
@eqs/cli@0.15.0
@eqs/auth@0.15.0
@eqs/tooling@0.14.0
@eqs/testing@0.14.0
@eqs/telemetry@0.14.0
@eqs/platform-next@0.14.0
@eqs/platform-nest@0.14.0
@eqs/http@0.14.0
@eqs/errors@0.14.0
@eqs/config@0.14.0
@eqs/cli@0.14.0
@eqs/auth@0.14.0
@eqs/tooling@0.13.0
@eqs/testing@0.13.0
@eqs/telemetry@0.13.0
@eqs/platform-next@0.13.0
@eqs/platform-nest@0.13.0
@eqs/http@0.13.0
@eqs/errors@0.13.0
@eqs/config@0.13.0
@eqs/cli@0.13.0
@eqs/auth@0.13.0
@eqs/tooling@0.12.2
@eqs/testing@0.12.2
@eqs/telemetry@0.12.2
@eqs/platform-next@0.12.2
@eqs/platform-nest@0.12.2
@eqs/http@0.12.2
@eqs/errors@0.12.2
@eqs/config@0.12.2
@eqs/cli@0.12.2
@eqs/auth@0.12.2
@eqs/tooling@0.12.1
@eqs/testing@0.12.1
@eqs/telemetry@0.12.1
@eqs/platform-next@0.12.1
@eqs/platform-nest@0.12.1
@eqs/http@0.12.1
@eqs/errors@0.12.1
@eqs/config@0.12.1
@eqs/cli@0.12.1
@eqs/auth@0.12.1
@eqs/tooling@0.12.0
@eqs/testing@0.12.0
@eqs/telemetry@0.12.0
@eqs/platform-next@0.12.0
@eqs/platform-nest@0.12.0
@eqs/http@0.12.0
@eqs/errors@0.12.0
@eqs/config@0.12.0
@eqs/cli@0.12.0
@eqs/auth@0.12.0
@eqs/tooling@0.11.2
@eqs/testing@0.11.2
@eqs/telemetry@0.11.2
@eqs/platform-next@0.11.2
@eqs/platform-nest@0.11.2
@eqs/http@0.11.2
@eqs/errors@0.11.2
@eqs/config@0.11.2
@eqs/cli@0.11.2
@eqs/auth@0.11.2
@eqs/tooling@0.11.1
@eqs/testing@0.11.1
@eqs/telemetry@0.11.1
@eqs/platform-next@0.11.1
@eqs/platform-nest@0.11.1
@eqs/http@0.11.1
@eqs/errors@0.11.1
@eqs/config@0.11.1
@eqs/cli@0.11.1
@eqs/auth@0.11.1
@eqs/tooling@0.11.0
@eqs/testing@0.11.0
@eqs/telemetry@0.11.0
@eqs/platform-next@0.11.0
@eqs/platform-nest@0.11.0
@eqs/http@0.11.0
@eqs/errors@0.11.0
@eqs/config@0.11.0
@eqs/cli@0.11.0
@eqs/auth@0.11.0
@eqs/tooling@0.10.1
@eqs/testing@0.10.1
@eqs/telemetry@0.10.1
@eqs/platform-next@0.10.1
@eqs/platform-nest@0.10.1
@eqs/http@0.10.1
@eqs/errors@0.10.1
@eqs/config@0.10.1
@eqs/cli@0.10.1
@eqs/auth@0.10.1
@eqs/tooling@0.10.0
@eqs/testing@0.10.0
@eqs/telemetry@0.10.0
@eqs/platform-next@0.10.0
@eqs/platform-nest@0.10.0
@eqs/http@0.10.0
@eqs/errors@0.10.0
@eqs/config@0.10.0
@eqs/cli@0.10.0
@eqs/auth@0.10.0
@eqs/tooling@0.9.0
@eqs/testing@0.9.0
@eqs/telemetry@0.9.0
@eqs/platform-next@0.9.0
@eqs/platform-nest@0.9.0
@eqs/http@0.9.0
@eqs/errors@0.9.0
@eqs/config@0.9.0
@eqs/cli@0.9.0
@eqs/auth@0.9.0
@eqs/tooling@0.8.1
@eqs/testing@0.8.1
@eqs/telemetry@0.8.1
@eqs/platform-next@0.8.1
@eqs/platform-nest@0.8.1
@eqs/http@0.8.1
@eqs/errors@0.8.1
@eqs/config@0.8.1
@eqs/cli@0.8.1
@eqs/auth@0.8.1
@eqs/tooling@0.8.0
@eqs/testing@0.8.0
@eqs/telemetry@0.8.0
@eqs/platform-next@0.8.0
@eqs/platform-nest@0.8.0
@eqs/http@0.8.0
@eqs/errors@0.8.0
@eqs/config@0.8.0
@eqs/cli@0.8.0
@eqs/auth@0.8.0
@eqs/tooling@0.7.0
@eqs/testing@0.7.0
@eqs/telemetry@0.7.0
@eqs/platform-next@0.7.0
@eqs/platform-nest@0.7.0
@eqs/http@0.7.0
@eqs/errors@0.7.0
@eqs/config@0.7.0
@eqs/cli@0.7.0
@eqs/auth@0.7.0
@eqs/tooling@0.6.0
@eqs/testing@0.6.0
@eqs/telemetry@0.6.0
@eqs/platform-next@0.6.0
@eqs/platform-nest@0.6.0
@eqs/http@0.6.0
@eqs/errors@0.6.0
@eqs/config@0.6.0
@eqs/cli@0.6.0
@eqs/auth@0.6.0
@eqs/tooling@0.5.1
@eqs/testing@0.5.1
@eqs/telemetry@0.5.1
@eqs/platform-next@0.5.1
@eqs/platform-nest@0.5.1
@eqs/http@0.5.1
@eqs/errors@0.5.1
@eqs/config@0.5.1
@eqs/cli@0.5.1
@eqs/auth@0.5.1
@eqs/tooling@0.5.0
@eqs/testing@0.5.0
@eqs/telemetry@0.5.0
@eqs/platform-next@0.5.0
@eqs/platform-nest@0.5.0
@eqs/http@0.5.0
@eqs/errors@0.5.0
@eqs/config@0.5.0
@eqs/cli@0.5.0
@eqs/auth@0.5.0
@eqs/tooling@0.4.0
@eqs/testing@0.4.0
@eqs/telemetry@0.4.0
@eqs/platform-next@0.4.0
@eqs/platform-nest@0.4.0
@eqs/http@0.4.0
@eqs/errors@0.4.0
@eqs/config@0.4.0
@eqs/cli@0.4.0
@eqs/auth@0.4.0
@eqs/tooling@0.2.0
@eqs/telemetry@0.2.0
@eqs/errors@0.2.0
@eqs/config@0.2.0
@eqs/tooling@0.1.0
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
Infrastructure/eqs-platform#62
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ce qui ne va pas
docker-compose.ymldu template nomme son projet d'aprèsSERVICE_NAME:et
.env.examplelivreSERVICE_NAME=template, plus des ports fixes :Deux satellites clonés depuis ce template, sur un même poste, ont donc le même nom de projet Compose. Le second
docker compose up -dne signale aucune collision : Docker Compose indexe les conteneurs et les volumes par nom de projet, il trouve le projettemplatedéjà levé, et il s'y attache.Le second clone se retrouve alors branché sur la base du premier — même hôte, même port, et les trois identifiants identiques puisqu'ils dérivent tous de
SERVICE_NAME:DATABASE_URL=postgresql://template:template@localhost:55400/template?schema=publicest valide dans les deux clones et désigne la même base. Unpnpm --filter api db:devoudb:deploydans le second clone écrit dans la base de développement du premier, et réussit.C'est l'inverse de ce que le dépôt promet. Le commentaire de
docker-compose.ymlaffirme :et
AGENTS.md: « Un port est réservé au clonage (docker-compose.yml). On ne le change pas. » Les deux décrivent l'état d'un satellite initialisé. Le template, lui, livre les valeurs du template, et rien dans le dépôt ne se plaint au moment où c'est dangereux.Reproduction
Obtenu : un seul projet,
template. Le seconduprend « Running » sans rien créer.docker compose psdepuissatellite-bmontre les conteneurs desatellite-a.Attendu : soit deux projets distincts, soit un refus explicite nommant le conflit.
La démonstration du dégât, ensuite : dans
satellite-a, créer une table et y écrire une ligne ; danssatellite-b, jouerpnpm --filter api db:dev. La migration debs'applique à la base dea.Ce que ça coûte
Bloquant, et silencieux — la pire combinaison. Le mode d'échec n'est pas une erreur, c'est un succès sur la mauvaise base.
Dans mon cas, un agent de codage a vu les ports 55400 et 43100 déjà pris par un autre satellite du même poste, a compris ce qui se passait, et a corrigé ses ports en local (55401 / 43101) dans un
.envnon versionné. Il s'en est sorti — mais il a été chanceux : les ports occupés lui ont donné un signal visible. AvecSERVICE_NAMEidentique, Compose ne donne aucun signal du tout, et corriger les ports ne suffit pas : tant queSERVICE_NAMErestetemplate, les deux clones se disputent le même projet et le même volume.La perte possible est la base de développement d'un autre projet, écrasée par les migrations d'un projet sans rapport. Sur un poste où tournent plusieurs satellites — ce que le clonage encourage — c'est une question de temps.
Ce que je proposerais
Par ordre d'utilité décroissante.
Ne pas livrer de valeur utilisable pour
SERVICE_NAME..env.exampleporteSERVICE_NAME=vide, et le schémabaseSchemal'exige non vide. Le clone échoue alors au premier démarrage, avec un message qui nomme la variable — au lieu de réussir sur la base du voisin. Le socle échoue au démarrage, jamais à la première requête : c'est déjà la doctrine deplateforme.ts, elle mérite de s'appliquer ici.Retirer la valeur par défaut du compose.
name: ${SERVICE_NAME:?SERVICE_NAME est requis}au lieu de${SERVICE_NAME:-template}. Compose refuse alors de lever quoi que ce soit sans nom de service, et le message le dit. Le:-templateest précisément ce qui transforme un oubli en collision muette.Même traitement pour les ports :
.env.exampleles laisse vides, etinitialiser-un-satellite.mda l'étape qui les pose depuis la plage réservée. Un port vide se voit ; un port juste-mais-pas-le-tien ne se voit pas.Un contrôle
eqs doctorqui refuseSERVICE_NAME=template(ou toute valeur égale au nom du template) dans un dépôt qui n'est pas le template lui-même. C'est l'écart qu'un clone oublie de fermer, et un contrôle le rattrape une fois pour trente dépôts.Si la 1 et la 2 vous semblent trop brutales pour l'ergonomie d'un clone neuf, je les défends quand même : le coût d'un démarrage qui échoue en nommant la variable est d'une minute, et le coût de l'alternative est la base de développement de quelqu'un d'autre.
Versions
@eqs/cli,@eqs/tooling,@eqs/testing:^0.8.1.docker-compose.ymlet.env.exampledu template, tels que livrés au clone.nest, constaté le 2026-09-12 sur Windows 11 / Docker Desktop.Contournement
Dans le clone concerné,
.envlocal (non versionné) :PORT_POSTGRES=55401,PORT_OTEL=43101. Ce contournement est insuffisant et je le dis explicitement : il évite la collision de ports, pas le partage de projet Compose, puisqueSERVICE_NAMErestetemplate. Il est à retirer quand le défaut est corrigé, et à ne pas prendre pour modèle.Correction sur le statut de la reproduction, à lire avant de traiter le ticket.
La section « Reproduction » est rédigée au mode observé — « Obtenu : un seul projet,
template» — et ce n'est pas exact. Je corrige.Ce qui est observé :
docker-compose.ymlnomme le projet${SERVICE_NAME:-template}et dérivePOSTGRES_USER,POSTGRES_PASSWORDetPOSTGRES_DBde la même variable ;.env.examplelivreSERVICE_NAME=templateet les quatre ports en dur.Ce qui est déduit, et non joué : le partage de projet Compose entre deux clones, l'attachement du second
upaux conteneurs du premier, et la migration du second clone s'appliquant à la base du premier. Cela découle de la façon dont Compose indexe par nom de projet, et les identifiants concordent — mais je n'ai pas exécuté la séquence à deux clones, et je n'ai pas vérifié la perte de données.La conséquence pratique : le défaut de conception (une valeur par défaut utilisable pour une variable qui doit être unique par clone) est établi par lecture et se corrige tel quel. Le scénario de dégât, lui, demande à être confirmé avant d'être cité comme un fait — et il devrait l'être, parce que c'est lui qui décide de la priorité.
Les quatre propositions restent valables indépendamment : elles ferment la cause, pas le symptôme.
Votre correction était la bonne chose à faire, et elle méritait qu'on y réponde par une exécution. Je l'ai jouée. Le scénario de dégât est confirmé.
Ce que j'ai exécuté
Deux clones réels du gabarit,
.envcopié de.env.exampledans chacun,docker compose up -d postgresdans les deux.Un seul projet :
Les deux clones voient le même conteneur — notez que
CONFIG FILESpointe sursat-apendant quesat-ble pilote :Et la partie que vous n'aviez pas jouée :
Une écriture depuis un clone se lit depuis l'autre. Une migration jouée dans
bs'applique donc bien à la base de développement dea, et réussit — le mode d'échec n'est pas une erreur, c'est un succès sur la mauvaise base, exactement comme vous l'écriviez.Un détail observé que vous n'aviez pas : le premier
upa affichéContainer template-postgres-1 Recreate. Un conteneur de ce nom traînait déjà sur le poste, hors de tout projet listé. C'est un symptôme de plus du même défaut : le nom est si générique qu'il se retrouve partout.(Nettoyé après coup : table supprimée, projet descendu sans
-v— le volume pouvait préexister à mon essai, et je n'allais pas détruire les données de quelqu'un pour vérifier qu'on peut détruire les données de quelqu'un.)Ce que j'ai pris
Vos options 1 et 2, ensemble, parce qu'elles ferment deux portes différentes.
docker-compose.yml:Vérifié, avec un
.envqui ne l'a pas rempli :Et
.env.examplelivreSERVICE_NAME=vide. Votre pari était juste :baseSchemaexige déjà/^[a-z][a-z0-9-]{1,40}$/, donc une valeur vide échoue au démarrage en nommant la variable. Le clone qui oublie échoue deux fois plutôt que de réussir sur la base du voisin.Vous écriviez : « si la 1 et la 2 vous semblent trop brutales pour l'ergonomie d'un clone neuf, je les défends quand même ». Elles ne le sont pas. Une minute de démarrage raté contre la base de développement de quelqu'un d'autre — l'arbitrage n'en est pas un.
Ce que je n'ai pas pris, et pourquoi
Votre option 3, vider les ports. Pendant la reproduction, j'ai eu ceci :
Un port déjà pris se voit, bruyamment, et s'attribue tout seul à la bonne cause. C'est précisément ce qui distingue les deux variables : le port collisionne visiblement, le nom de projet collisionne en silence. Vider les ports ajouterait un échec de démarrage à chaque clone pour un problème qui se signale déjà. Si vous pensez qu'il y a un cas où un port valide-mais-pas-le-vôtre passe inaperçu, dites-le — ça changerait l'arbitrage.
Votre option 4, le contrôle
eqs doctor. Elle mérite d'être prise, et elle ne pouvait pas l'être maintenant :@eqs/*0.9.0 vient d'être publié en candidate il y a vingt minutes, et l'ajouter rouvrirait une version déjà vérifiée deux fois. Elle part dans la suivante. Elle garde sa valeur propre : les clones déjà faits portentSERVICE_NAME=templatedans leur.envlocal, et ni le compose ni le schéma ne les rattraperont — c'est le contrôle qui les trouvera.Votre contournement
Vous aviez raison de le déclarer insuffisant, et de dire pourquoi. Après cette correction, il devient inutile : remplissez
SERVICE_NAMEavec un nom propre au satellite et les ports redeviennent une question d'hygiène, pas de sécurité.Branche
fix/service-name-uniquesur le gabarit, tirée demain, fusionnable tout de suite — elle ne dépend d'aucune version du socle.AGENTS.mddu template affirme « Push surmain→ Coolify » comme un fait, alors qu'un satellite neuf n'est déployé nulle part #6100du gabarit sans figurer dansreference/satellites.md, et rien ne le détecte #74Corrigé, côté gabarit.
docker-compose.ymlne retombe plus sur un nom de projet par défaut :.env.examplelivreSERVICE_NAME=vide, avec le pourquoi au-dessus : « Compose en fait son nom de projet, et les trois identifiants Postgres en dérivent. Deux clones qui le partagent parlent à la même base sans que rien ne le dise. » Vide, Compose refuse de lever et le nomme.Le ticket a eu une suite : il a mis en lumière que
SERVICE_NAMEne gouverne pas les ports, et c'est ce qui a donné #74 puiscli.ports-du-gabarit(D-047). Deux clones ne se mélangent plus ni par la base, ni par les ports.Fermé après vérification contre le gabarit à jour le 2026-09-21. Ce ticket restait ouvert faute d'un
Closes #62dans le commit, pas faute de correctif.nomfixe fait partager la base de test à deux checkouts du même dépôt qui tournent en même temps #174