Le Dockerfile énumère les manifestes en dur : ajouter un workspace casse docker build avec un lockfile réputé périmé #7
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?
Constaté le 2026-09-09 sur le premier satellite réel monté depuis le template, pendant le cadrage du projet Pilotage.
@eqs/*0.7.0,eqs --version0.7.0, pnpm 11.8.0.Le
Dockerfilecopie les manifestes un par un dans ses étagesdepsetprod-deps, alors quepnpm-workspace.yamldéclareapps/*etpackages/*. Dès qu'un satellite ajoute un workspace — un package souspackages/, ou une troisième application sousapps/— l'image ne se construit plus, et l'échec ne dit pas ce qui manque.Où
Dockerfile, étagesdeps(l. 19-22) etprod-deps(l. 60-63) :Face à
pnpm-workspace.yaml:Le lockfile décrit N importers ; le contexte de build n'en fournit que deux.
pnpm install --frozen-lockfilerefuse.Reproduction
Dans un clone du template, après
pnpm install:L'échec est un
ERR_PNPM_OUTDATED_LOCKFILEsur un lockfile qui vient d'être régénéré et qui est correct. Le message oriente vers « relancepnpm install», qui ne corrige rien : le défaut n'est pas dans le lockfile, mais dans ce que le contexte de build en montre.Ce que j'attendais, et pourquoi
pnpm-workspace.yamldéclarepackages/*: le dépôt annonce donc que des workspaces peuvent y apparaître. LeREADMEva dans le même sens — «packages/est vide et le reste **tant qu'**un même code ne sert pas aux deux applications » — ce qui décrit une attente, pas une interdiction.Rien dans le
Dockerfilene prévient du contraire, et ses commentaires sont par ailleurs très explicites sur ses autres contraintes (le fragment Prisma, le.npmrc, l'empreinte du magasin pnpm). L'absence de mention ici se lit comme « rien à signaler ».Ce que ça coûte
Bloquant, et tardif — c'est ce qui le rend cher. La porte
pre-push(pnpm check) reste verte, la CIcheckreste verte : rien ne construit d'image. Le défaut n'apparaît qu'au déploiement Coolify, c'est-à-dire après le push surmain, sur un service déjà en production (D-013).Un satellite qui découpe son code — le geste normal quand une application grossit — casse donc sa production sans qu'aucun contrôle local ne l'ait dit.
Ce que je proposerais
eqs doctorqui compare les motifs depnpm-workspace.yamlaux chemins copiés dans leDockerfile. C'est le geste qui ramène la détection avant le push, là où la doctrine veut la porte.packages/, et l'écrire. Si un satellite ne doit jamais produire de package,packages/*n'a pas à figurer danspnpm-workspace.yaml, et leREADMEdevrait l'énoncer comme une règle et non comme une étape. Si l'inverse est retenu, il manque une décision sur le versionnement et la publication : un package produit par un satellite n'a aujourd'hui ni cible de construction, ni étape de publication dans.forgejo/workflows/ci.yml.Contournement en place
Aucun. Le satellite concerné ajoutera
apps/mcpet butera dessus ; le signalement précède l'implémentation.Non reproduit — deux fois, en cherchant l'échec.
Le raisonnement du ticket est bon :
pnpm-workspace.yamldéclareapps/*alors que leDockerfilecopieapps/api/package.jsonetapps/web/package.jsonun par un. Ajouter un workspace devrait faire diverger le verrou de ce que voit l'image. Ce n'est pas ce qui se passe.Mesure 1 — un
packages/exempleavec un manifeste nu,packages/*ajouté au workspace,pnpm installpour mettre le verrou à jour :Mesure 2 — le même, mais avec une vraie dépendance externe et un script
build. C'est le cas que le ticket décrit, celui où le verrou porte quelque chose que l'étagedepsne peut pas voir :Les deux étages passent, et le paquet se construit.
Pourquoi :
--frozen-lockfilen'installe pas depuis les manifestes, il installe depuis le verrou — qui décrit déjà tous les importers, y compris celui dont lepackage.jsonn'a pas été copié. pnpm constate que ce manifeste manque, saute cet importer au lieu d'échouer, et ses dépendances arrivent quand même dans le magasin. À l'étagebuild,COPY . .amène le manifeste manquant etpnpm -r buildrecrée les liens.ERR_PNPM_OUTDATED_LOCKFILEsanctionne un verrou en retard sur les manifestes ; ici le verrou est en avance, ce qui est le sens toléré.Le correctif proposé, je ne l'ai pas appliqué :
COPY apps/*/package.json apps/aplatit les chemins. Docker ne reproduit pas l'arborescence : les deux fichiers atterrissent surapps/package.jsonet se recouvrent. C'est unCOPYqui réussit et une image fausse — pire que le défaut supposé.Ce qui est vrai dans le ticket, et que je garde : on s'appuie sur une tolérance de pnpm que rien ne documente. Le jour où elle se resserre, les images de toute la flotte cassent d'un coup, et l'énumération n'aura prévenu personne. C'est le bac à sable (
eqs-demo,--depot <chemin>) qui doit l'attraper — il construit et déploie le gabarit pour de vrai avant chaque sortie du socle (D-037). LeDockerfileporte maintenant tout ça en commentaire, mesure et date incluses, pour que le prochain à s'en inquiéter trouve la réponse sur place.Votre question sur
packages/a une réponse, et elle est tranchée : un satellite ne produit pas de paquet publiable. Si du code doit être partagé, il part dans son propre dépôt, contrôlable et versionné — pas dans un satellite.packages/*est donc retiré depnpm-workspace.yaml, il ne reste qu'apps/*, et le README le dit comme une règle. C'est D-041 danseqs-platform. L'écart qui vous a mis la puce à l'oreille n'existe plus : le workspace déclare exactement les deux emplacements que leDockerfileénumère.Je laisse ce ticket ouvert. Je contredis votre diagnostic sur la foi de deux mesures qui peuvent différer des vôtres — la version de pnpm, l'état du verrou au moment du build, un
pnpm installlocal qui aurait tourné entre-temps. Si vous avez la sortie exacte deERR_PNPM_OUTDATED_LOCKFILE(pnpm imprime les importers en cause), elle tranche en une lecture. Et si votre satellite avait bien unpackages/au moment de l'échec, dites-le : le retrait ci-dessus le rend sans objet, mais ça expliquerait l'écart entre nos deux mesures et c'est ce qu'il faut savoir.Les sorties exactes — et une rectification : je me suis trompé sur le symptôme
Nous avons ajouté un troisième workspace (
apps/mcp) et joué le build pour de vrai. Mon corps de ticket décrit mal ce qui se passe, et la réalité est plus insidieuse que ce que j'annonçais.Ce que j'avais annoncé, et qui est faux
J'écrivais que
pnpm install --frozen-lockfileéchouerait avec unERR_PNPM_OUTDATED_LOCKFILE. Il n'échoue pas. Il n'y a aucune erreur, aucun avertissement, et le build continue.Ce qui se passe réellement
L'étage
deps, avec leDockerfiled'origine, sur un dépôt qui compte quatre workspaces :Scope: all 3 workspace projects— alors que le lockfile en déclare quatre. pnpm installe silencieusement le sous-ensemble dont il voit les manifestes, et n'en dit rien : ceux qu'on ne lui a pas copiés n'existent tout simplement pas de son point de vue.Après correction, le même étage :
Une installation partielle muette, donc, et pas un échec franc. C'est pire que ce que j'annonçais : un échec aurait au moins arrêté le build. Là, l'image se construit — avec un workspace en moins. Chez nous, l'étage
buildrattrapait le manquant par une réinstallation implicite, ce qui masquait complètement le problème et le rendait dépendant de l'ordre des étages. Sur un autre montage, l'image partirait en production amputée.Le signal existe pourtant, et il est parfaitement lisible : ce
Scope: all N workspace projectsque personne ne regarde. Un contrôle qui compare ce N au nombre d'importers du lockfile serait sans doute plus simple à écrire qu'un contrôle qui relit leDockerfile.Ma proposition n°1 ne marche pas — le joker aplatit les chemins
J'avais proposé « copier les manifestes par motif ». Testé :
Docker aplatit les chemins des sources : les trois fichiers atterrissent sur un unique
apps/package.json, chacun écrasant le précédent. pnpm ne retrouve aucun workspace.COPY --parents apps/*/package.json ./préserverait l'arborescence, mais--parentsn'existe que sur le canal expérimental de BuildKit (# syntax=docker/dockerfile:1-labs). Nous l'avons écarté : ça n'a pas sa place dans un gabarit destiné à trente dépôts.Nous avons donc fait le plus bête — une ligne
COPYexplicite par workspace, dans les deux étages. Ça marche, et ça ne résout évidemment pas le problème de fond : le quatrième workspace du prochain satellite reproduira exactement le même trou.Vérifié après correction
Les deux cibles, sur un dépôt à quatre workspaces. C'était la première construction d'image jouée sur ce satellite.
Un détail qui a coûté du temps et qui vous intéressera peut-être
Le
postinstalld'apps/mcprégénère un client depuisopenapi.json. L'étagedepsne copie que les manifestes : il a donc fallu y ajouter les fichiers dont cepostinstalla besoin — exactement comme les trois lignes déjà présentes pour le fragment Prisma d'apps/api, et pour la même raison, documentée dans leDockerfile.Autrement dit, ce n'est pas un cas particulier : tout workspace avec un
postinstallnon trivial devra ajouter ses propres lignes à l'étagedeps. Si la copie des manifestes est un jour repensée, ce point mérite d'entrer dans la réflexion.Je corrige le corps du ticket ? Dites-moi si vous préférez que je le réécrive ou que ce commentaire suffise.
Votre
Scope: all 3 workspace projectsest la ligne qui manquait, et elle renverse ma conclusion.J'avais mesuré que
--frozen-lockfilen'échoue pas sur un manifeste absent, et j'en avais tiré « le cas est bénin ». C'est l'inverse : pnpm installe le sous-ensemble qu'il voit, sans un mot, et l'image se construit — amputée. Un échec franc aurait au moins arrêté le build. J'ai pris l'absence d'erreur pour une absence de problème, et c'est exactement la faute que le socle est censé ne pas commettre.Chez moi le manquant était rattrapé à l'étage suivant, comme chez vous — c'est ce qui m'a fait conclure trop vite. Vous le dites mieux que moi : le résultat dépend de l'ordre des étages, ce qui est la définition d'un défaut qui attend son moment.
Et vous confirmez de première main les deux impasses que j'avais annoncées sans les avoir jouées en Docker : le joker aplatit les chemins, et
COPY --parentsdemande le canal expérimental de BuildKit. Merci de les avoir vérifiées plutôt que de me croire.Ce que ça donne : un contrôle, puisqu'il n'y a pas de raccourci.
tooling.workspaces-dockerfile, dans@eqs/tooling, bloquant et statique :importersdepnpm-lock.yaml— ce que pnpm lit vraiment dans l'image — et les compare auxCOPY …/package.jsonduDockerfile;pnpm install. Un manifeste copié dansdepset oublié dansprod-depsrefait le trou dans l'image finale, celle qui part en production. C'est le cas que votre correction manuelle aurait pu laisser passer.Vérifié dans les deux sens : il nomme les deux cas, et il se tait sur un
Dockerfilecorrect. Vert sur le gabarit tel qu'il est.Sur votre proposition de contrôler le
Scope: all N: elle est plus élégante, et je ne l'ai pas retenue pour une raison de placement. Cette ligne n'existe qu'au moment dudocker build, c'est-à-dire après le push surmain, sur un service déjà déployé (D-013). Lire leDockerfileest moins joli mais tourne danspnpm check, avant que les commits ne quittent la machine — là où la doctrine veut la porte. Le vrai argument contre votre version est le moment, pas la difficulté.Sur les
postinstallnon triviaux : point noté, et c'est le meilleur du commentaire. Le contrôle ne le voit pas, et je l'écris dans son code plutôt que de le laisser se découvrir — tout workspace qui régénère quelque chose à l'installation devra ajouter ses propres lignes à l'étagedeps, commeapps/apile fait déjà pour le fragment Prisma. Si la copie des manifestes est un jour repensée, c'est la contrainte qui décidera de la forme.Ne réécrivez pas le corps du ticket : ce commentaire est plus utile que le corps corrigé — on y voit ce qu'on croyait, ce qu'on a mesuré, et l'écart entre les deux. C'est ce qu'on relit dans six mois.
Branche
fix/workspaces-dockerfile. Je ferme.Repris en 0.8.0 — la réponse est meilleure que ma proposition, mais elle ne se charge pas
Repris le 2026-09-10 sur le satellite qui avait ouvert ce ticket.
Sur la façon dont vous l'avez fermé
Vous n'avez pas modifié le
Dockerfile, et vous avez eu raison : le gabarit ne connaît que deux workspaces et ne peut pas deviner ceux d'un satellite. À la place, un contrôle statique —tooling.workspaces-dockerfile— qui compare lesimportersdu lockfile auxCOPY …/package.json.C'est plus fin que ce que je proposais. Ma piste n°1 (copier par motif) ne marchait pas — Docker aplatit les chemins — et ma piste n°2 (un contrôle qui relit le
Dockerfile) était la bonne mais moins bien posée : partir du lockfile plutôt que du fichier attrape aussi le cas où quelqu'un ajoute un workspace sans y penser. Le signalScope: all N workspace projectsque je citais est exactement ce que votre contrôle rend inutile à surveiller à la main.Le problème : nous n'avons pas pu le vérifier
scripts/n'est pas dans le champfilesde@eqs/tooling@0.8.0. L'import étant en tête de module, tous les contrôles du paquet tombent avec lui. Ticket ouvert : eqs-platform#44.Donc : le défaut de ce ticket-ci est bien traité en intention, mais le garde-fou qui devait le fermer ne tourne chez personne aujourd'hui. Nous le rejouerons dès la republication et je viendrai le confirmer ici.
Ce que nous gardons, et qui n'est plus un contournement
Nos lignes
COPY apps/mcp/…et les fichiers supplémentaires d'apps/webrestent, mais leur commentaire a été réécrit : ce n'est plus « un contournement en attente d'arbitrage amont », c'est notre propre besoin — nous avons un troisième workspace que le gabarit ignore légitimement.Une chose que la vérification a révélée, et qui pourrait vous intéresser
En jouant
docker buildpour de vrai, nous avons découvert que notre copie était incomplète pour nos propres besoins :apps/weba, commeapps/mcp, unpostinstallqui régénère un client Orval. Il a fallu ajouter à l'étagedepsleurstsconfig.json, leursorval.config.ts, et pourapps/weble module de mutateur que sa configuration référence.C'est le même mécanisme que les trois lignes déjà présentes pour le fragment Prisma d'
apps/api, et documentées comme telles dans leDockerfile. Autrement dit : tout workspace avec unpostinstallnon trivial doit ajouter ses propres fichiers à l'étagedeps, et votre contrôle ne le verra pas — il compare des manifestes, pas des besoins depostinstall.Ce n'est pas une critique du contrôle, qui fait ce qu'il annonce. Juste un cas voisin qui restera à la charge de chaque satellite, et qui ne se manifeste qu'à la construction d'image.
Vérifications faites ici
Quatre workspaces,
Scope: all 4 workspace projectsaux deux étages.