Skip to content

ADR 030 — Layout du dépôt et périmètre du contexte de build (2026-08-11)

Statut

Accepté et implémenté (2026-08-16). Proposé le 2026-08-11. Issue #161. Fait suite à #157, dont le nettoyage du .dockerignore est livré comme mesure d'attente, explicitement assumée comme une rustine. Sprint 6 (multi-environnements).

Vérifié après implémentation : /usr/src/app ne contient plus que app/, alembic/, alembic.ini et les deux requirements*.txt. Taille inchangée à 223M, comme la mesure du 2026-08-11 l'annonçait — le gain n'a jamais été le poids.

Contexte

Le projet sert deux workloads : l'API FastAPI et la SPA React (ADR 028). Les deux sont construits en image par kaniko dans le même pipeline, mais pas de la même manière :

  • le frontend est construit avec --context $CI_PROJECT_DIR/frontend, son Dockerfile et son .dockerignore vivant dans ce dossier ;
  • le backend est construit avec --context $CI_PROJECT_DIR, soit la racine du dépôt, et son Dockerfile se termine sur COPY . ..

Le backend embarque donc tout ce que le .dockerignore n'a pas pensé à exclure. Constaté dans l'image réelle en instruisant #157 : k8s/, scripts/, .gitlab/, .gitlab-ci*.yml, .vex/, .trivyignore, .claude/, mkdocs.yml, docs-requirements.txt, renovate.json et les aws-*.sh étaient présents dans /usr/src/app.

Le coût n'est pas la taille de l'image (224M contre 223M une fois nettoyée), c'est le couplage : modifier un overlay Kustomize changeait le contenu de l'image backend, donc une MR de promotion ou de documentation faisait redéployer dev pour un changement qui ne le concerne pas. C'est l'un des mécanismes par lesquels staging ne rattrape jamais dev (#157).

Décision — le contexte de build est un sous-dossier applicatif, jamais la racine

Le backend est déplacé sous backend/, symétrique de frontend/ : son Dockerfile, son .dockerignore, son code, ses migrations, ses dépendances et ses tests y vivent, et la CI le construit avec --context $CI_PROJECT_DIR/backend.

Le nom des images ECR ne change pas, donc aucun impact sur k8s/, sur les overlays ni sur GitOps.

Pourquoi une structure plutôt qu'une liste

Un .dockerignore énumère ce qu'on ne veut pas. Cette liste croît avec le dépôt, elle est muette quand elle est incomplète, et rien dans la CI ne signale un oubli : le pipeline reste vert, l'image grossit d'un dossier que personne n'a vu passer. .claude/ en est la démonstration, il n'apparaissait même pas dans l'inventaire de

157 et n'a été trouvé qu'en listant /usr/src/app dans l'image construite.

Un contexte restreint énumère ce qu'on veut. Ajouter un dossier à la racine du dépôt n'a alors plus aucun effet sur l'image, sans qu'aucune liste n'ait à être tenue à jour. La propriété recherchée n'est pas « la liste est complète aujourd'hui » mais « la question ne se pose plus ».

Bénéfice annexe, hors de portée d'un .dockerignore : docker-compose-dev.yaml monte ./:/usr/src/app:ro, soit la racine du dépôt dans le conteneur de développement local. Seul le déplacement corrige ce point.

Ce que la décision ne résout pas

Elle ne stabilise pas le digest de l'image. Mesuré le 2026-08-11 : deux builds sans le moindre changement de contexte produisent deux digests différents, 6 couches sur 10 dérivant spontanément à cause des instructions RUN (apt-get --only-upgrade, reconstruction du venv, suppression des pip). C'est indépendant du contexte de build.

Ce point est écrit ici parce que le Done when initial de #157 promettait cette stabilité au nettoyage du .dockerignore, ce que la mesure a démenti. La même promesse ne doit pas être reportée sur le présent ADR.

L'idempotence de la promotion relève d'un autre levier : ne pas déclencher de build du tout quand aucun fichier applicatif n'a changé (filtrage par chemin, #157 levier 2), éventuellement complété par un build reproductible.

Alternatives écartées

S'en tenir au .dockerignore. C'est l'état livré par #157. Suffisant à un instant donné, mais le mécanisme d'oubli reste entier et se rejouera au prochain dossier ajouté à la racine.

kaniko --reproducible. Normalise les timestamps et viserait le digest, pas le contenu. Orthogonal au problème traité ici, et non testé à ce jour : à instruire séparément avant d'y compter.

Deux dépôts séparés. Isole parfaitement les contextes mais fait perdre l'atomicité d'une MR touchant l'API et son manifeste, et dupliquerait la chaîne GitOps. Hors de proportion avec le problème.

Conséquences

La MR doit être atomique : le déplacement, les chemins CI (--context, --dockerfile, le pip install du job pytest, SAST_EXCLUDED_PATHS, DS_EXCLUDED_PATHS) et docker-compose-dev.yaml changent ensemble, sinon le pipeline casse en cours de route. Le risque est concentré sur la CI, pas sur le code applicatif.

Deux points vérifiés qui allègent le travail : alembic.ini utilise script_location = %(here)s/alembic et suit donc son dossier sans modification, et il n'existe ni pyproject.toml, ni pytest.ini, ni setup.cfg à la racine, donc aucune configuration d'outil à repointer.

Le dépôt gagne une règle lisible sans documentation : un workload est un sous-dossier, son contexte de build est ce sous-dossier.

Ce que l'implémentation a révélé en plus (2026-08-16)

L'inventaire d'impact de #161 était juste mais incomplet. Quatre points s'y sont ajoutés, dont deux auraient produit une panne silencieuse.

example_data/ devait suivre. example_data/seed_db.py importe app.database, app.models et app.utils. Laissé à la racine, il ne résout plus ses imports.

Le rapport de couverture change de chemin. Les tests importent from app.main import app, donc pytest tourne désormais depuis backend/ et écrit backend/coverage.xml. Le job déclarait path: coverage.xml. Un artefact introuvable ne fait pas échouer le job : le pipeline serait resté vert et le widget de couverture aurait disparu de la MR sans un mot.

.env et .env.docker devaient suivre aussi. app/config.py déclare env_file=".env", chemin résolu relativement au répertoire courant. Depuis backend/, un .env resté à la racine devient invisible et Settings() échoue au démarrage. Les deux fichiers sont gitignorés : le déplacement est un geste local, donc invisible en revue, d'où sa mention ici et dans local-development.md.

Le Dockerfile n'a eu besoin d'aucune modification. COPY requirements.txt . et COPY . . sont relatifs au contexte, pas à la racine du dépôt. Même chose pour docker-compose-prod.yaml, qui tire l'image du registry et ne construit rien — seuls ses deux env_file ont été repointés.