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, sonDockerfileet son.dockerignorevivant dans ce dossier ; - le backend est construit avec
--context $CI_PROJECT_DIR, soit la racine du dépôt, et sonDockerfilese termine surCOPY . ..
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.