Développement web

Conteneurs de dev, CI et prod : ce qui peut être commun et ce qui doit différer

Jérémy Chomel Dawap
  • Publié le : 28 décembre 2025
  • Mis à jour le : 19 août 2026
  • Temps de lecture : 15 minutes
  1. Définir ce qui doit rester identique
  2. Partager l’image plutôt que tout l’environnement
  3. Verrouiller les dépendances et les artefacts
  4. Préserver l’ergonomie du développement local
  5. Faire de la CI un arbitre reproductible
  6. Réduire la surface du runtime de production
  7. Injecter configuration et secrets au bon moment
  8. Séparer migrations, données et démarrage
  9. Cas concret : une application Symfony à trois environnements
  10. Prouver la provenance de l’image déployée
  11. Aligner santé, logs et arrêt propre
  12. Savoir quand cette discipline devient rentable
  13. Éviter les fausses parités entre environnements
  14. Décider ce qui est commun, dérivé ou interdit
  15. Plan d’action sur six semaines
  16. Guides complémentaires pour fiabiliser la livraison
  17. Conclusion : promouvoir une preuve, pas une reconstruction
Portrait de Jérémy Chomel

Une équipe corrige un défaut dans le conteneur local, la CI reconstruit une image légèrement différente et la production exécute encore une troisième variante. Les trois portent le même commit, pourtant l’une contient Xdebug, l’autre compile les assets avec une version de Node distincte et la dernière télécharge Composer au démarrage. Quand l’incident arrive, personne ne sait si le code, l’image ou la configuration a réellement changé.

Le vrai sujet n’est pas d’obtenir trois environnements identiques. Le poste d’un développeur doit permettre l’édition et le diagnostic, la CI doit être jetable et déterministe, tandis que la production doit rester minimale, non interactive et gouvernée. La cible utile est une chaîne commune de construction qui produit un artefact identifiable, puis des contrats d’exécution adaptés sans réinterpréter ce qui a été testé.

Contre-intuitivement, ajouter des variantes explicites réduit la dérive. Un target de développement nommé, un target de test et un runtime final documentent mieux la différence qu’une image universelle activée par dix variables. Le coût caché vient des exceptions implicites : extension installée à la main, bind mount qui masque un fichier absent ou secret copié pendant le build.

Pour une équipe qui livre une application en développement web sur mesure, la décision doit relier Dockerfile, pipeline, registry et runbook. La méthode distingue le socle réellement partageable, les écarts légitimes et les contrôles à exécuter avant de promouvoir exactement le même digest jusqu’en production.

Définir ce qui doit rester identique

Partager le contrat logiciel

Les environnements doivent s’accorder sur les versions majeures du runtime, les extensions système, les dépendances résolues, le code compilé et le format des assets. Ils doivent aussi appliquer les mêmes migrations, règles de validation et schémas de messages. Cet invariant protège le comportement applicatif ; il n’impose ni le même nombre de processus ni les mêmes outils de confort.

Le contrat se matérialise par un Dockerfile versionné, des fichiers de verrouillage, un contexte de build maîtrisé et un digest d’image. La CI produit l’image une fois. Les étapes suivantes la promeuvent en changeant configuration, secrets, capacité et exposition réseau. Si la production reconstruit depuis Git, la preuve obtenue en test ne porte plus sur l’artefact réellement exécuté.

Nommer les différences autorisées

Développement autorise un montage du code, un débogueur et parfois un serveur d’assets. La CI active les outils de test et crée des dépendances éphémères. La production retire ces outils, utilise un utilisateur non privilégié et reçoit des ressources bornées. Une matrice courte attribue chaque différence à un besoin, un propriétaire et un test ; toute autre divergence est traitée comme une anomalie.

Partager l’image plutôt que tout l’environnement

Une image contient le runtime et l’application ; elle ne décrit pas à elle seule la topologie complète. Base de données, stockage objet, files, certificats, DNS et observabilité varient naturellement. Chercher à embarquer ces services dans un conteneur unique pour simuler la parité transforme le build en machine virtuelle opaque et complique chaque reprise.

Les bonnes pratiques officielles de build Docker recommandent notamment les builds multi-stage, les conteneurs éphémères et la limitation des paquets. Un stage commun peut préparer PHP et ses extensions ; un stage de build installe les dépendances et compile les assets ; le runtime final ne reçoit que les sorties nécessaires.

Éviter l’image « couteau suisse »

Installer compilateurs, client de registry, éditeur, shell d’administration et outils de test dans le runtime facilite une intervention ponctuelle mais augmente la surface de vulnérabilité et rend le diagnostic dépendant d’actions manuelles. Les opérations de support doivent vivre dans des commandes versionnées ou un conteneur d’outillage séparé, construit depuis la même base et lancé avec des droits temporaires.

Verrouiller les dépendances et les artefacts

Le fichier de verrouillage Composer ou npm constitue une entrée du build, pas une suggestion. La CI échoue s’il est incohérent avec le manifeste. Elle utilise un cache pour accélérer le téléchargement, mais ce cache ne remplace jamais le résultat résolu. Les dépendances système et l’image de base reçoivent également une stratégie de mise à jour et un digest lorsque la reproductibilité l’exige.

Une image n’est pas éternellement sûre parce qu’elle est immuable. L’équipe reconstruit régulièrement avec une base corrigée, exécute les tests, scanne les composants puis publie un nouveau digest. Elle distingue donc reproductibilité du build et fraîcheur de sécurité : pouvoir reproduire une image vulnérable ne dispense pas de la remplacer.

Rendre le cache vérifiable

Les couches stables sont placées avant le code changeant, mais la clé de cache inclut les fichiers qui déterminent réellement la sortie. Un cache empoisonné ou trop large peut réutiliser un vendor obsolète. Un build hebdomadaire sans cache et un contrôle de somme sur les artefacts donnent un signal simple sans condamner chaque pipeline à repartir de zéro.

Préserver l’ergonomie du développement local

Le poste local privilégie la boucle modifier, exécuter, tester. Le code peut être monté depuis l’hôte, tandis que vendor, cache et données volumineuses restent dans des volumes adaptés. L’environnement expose seulement les ports utiles et fournit une commande d’initialisation idempotente. Un nouvel arrivant doit obtenir une page et un test vert sans connaître l’ordre secret de cinq scripts.

La parité ne justifie pas une lenteur permanente. Si une suite ciblée dépasse trente secondes à cause du partage de fichiers, l’équipe mesure les appels système, le cache et la base avant d’augmenter la machine. Ce seuil est local à la boucle de travail : il sert à ouvrir un diagnostic, pas à définir une norme universelle pour Docker.

Garder les outils hors du chemin nominal

Xdebug, profiler et watchers doivent pouvoir être activés par profil. Les laisser actifs par défaut dégrade les mesures et masque parfois un goulot applicatif. Le développeur choisit le mode, mais la CI couvre le runtime sans ces aides afin que l’artefact testé ressemble bien à celui qui sera promu.

Faire de la CI un arbitre reproductible

La CI part d’un workspace propre, construit les targets, exécute analyse statique, tests, migrations de validation et contrôle du conteneur. Elle ne monte pas le vendor du runner et ne dépend pas d’un daemon préparé à la main. Chaque job conserve commit, digest, versions des outils et résultats, ce qui permet de relier une production à la preuve qui l’a autorisée.

Les services de test restent jetables, mais leur état initial doit être explicite. Une base est créée depuis migrations ou snapshot versionné ; une file est vidée entre scénarios ; les horloges et appels externes sont contrôlés. Un test vert obtenu grâce à des données survivant d’un job précédent est une dérive de CI, même si l’image applicative est correcte.

Tester l’image finale

Compiler dans un target puis tester uniquement le répertoire source laisse un angle mort. Le pipeline lance le runtime final, vérifie la route de santé, une commande console et l’absence des outils interdits. Il peut ensuite attacher une signature ou une attestation au digest. La règle de promotion refuse une image dont le scan ou la provenance n’est plus associé au résultat.

Réduire la surface du runtime de production

Le conteneur de production démarre sans téléchargement, sans compilation et sans migration automatique concurrente. Il exécute un processus principal, écrit ses logs vers la sortie standard et stocke les données durables hors de sa couche modifiable. Un filesystem en lecture seule et un utilisateur non root deviennent possibles lorsque cache et répertoires temporaires sont précisément déclarés.

La capacité, les timeouts et le nombre de workers appartiennent au déploiement, pas au Dockerfile. Deux environnements peuvent utiliser le même digest avec des ressources différentes. Le contrat reste valide si les limites sont testées : mémoire insuffisante, arrêt demandé, file en retard et dépendance indisponible doivent produire un état observable plutôt qu’un redémarrage silencieux.

Préparer un arrêt propre

Le serveur web cesse d’accepter de nouvelles requêtes, le worker termine ou abandonne proprement le message selon son contrat, puis le processus sort avant la fin du délai. Un timeout arbitraire peut tuer un effet externe juste avant son acquittement. Le runbook relie donc signal, grace period, idempotence et nouvelle livraison.

Injecter configuration et secrets au bon moment

Une variable non sensible peut paramétrer URL, capacité ou mode fonctionnel. Un secret nécessite une distribution, une rotation et un journal d’accès. Aucun des deux ne doit modifier le code compilé. Le même digest reçoit un jeu validé par environnement ; un contrôle de démarrage refuse les valeurs absentes ou incompatibles avant que le trafic n’arrive.

Les identifiants privés requis pendant le build ne passent ni par ARG ni par ENV. La documentation Docker sur les build secrets prévoit des montages temporaires pour ce besoin. Le secret sert à télécharger une dépendance puis disparaît de la couche ; un test inspecte néanmoins l’historique et le contenu final.

Refuser le fichier universel copié partout

Un fichier .env transmis par messagerie mélange souvent valeurs locales, production et secrets anciens. L’équipe conserve les noms et contraintes dans le dépôt, les valeurs sensibles dans un gestionnaire dédié et les paramètres déployés dans une configuration versionnée. La production peut alors être reconstruite sans redemander la mémoire d’un administrateur.

Séparer migrations, données et démarrage

L’image embarque le code de migration, mais son démarrage ne doit pas permettre à vingt réplicas de lancer la même opération. Un job unique exécute les étapes compatibles avant ou après le déploiement selon le plan. Le rollback applicatif reste possible tant que le schéma accepte les deux versions ; une suppression destructive attend la fin de cette fenêtre.

Les fixtures locales et les données de test ne rejoignent pas la production. Les sauvegardes ne vivent pas dans l’image et un volume n’est pas, à lui seul, une stratégie de restauration. La répétition de reprise utilise le même runtime d’outillage, mais elle vérifie cohérence des données, messages en attente et version applicative attendue.

Bloquer le démarrage incompatible

L’application expose une vérification de compatibilité de schéma sans lancer de transformation. Si la version requise manque, le déploiement reste hors trafic et l’alerte nomme la migration. Ce refus précoce coûte moins qu’un mélange de réplicas lisant deux formes de données non prévues.

Cas concret : une application Symfony à trois environnements

Cas concret hypothétique. Une application de commandes utilise PHP-FPM, Nginx, Messenger et un build Encore. En local, le code et les sources front sont montés ; un target dev ajoute Xdebug et Node. La CI construit les assets et vendor, lance les tests dans le target test, puis publie le runtime PHP et l’image Nginx référencés par digest.

En préproduction et production, aucun code n’est monté. Les deux environnements reçoivent les mêmes digests ; seuls secrets, URLs, capacité et règles réseau changent. Un job applique la migration compatible, puis dix pour cent du trafic ouvre sur la nouvelle version. Les workers anciens continuent de comprendre les messages en attente pendant la bascule.

Le pilote couvre cinquante déploiements et trois interruptions simulées. Les seuils locaux demandent zéro reconstruction après publication, zéro outil de debug dans le runtime, une corrélation complète entre commit et digest, et un repli sous quinze minutes sans restauration de base. Une seule image non traçable suspend l’extension ; ces valeurs qualifient cette équipe et non tous les projets.

Prouver la provenance de l’image déployée

Un tag lisible facilite la navigation, mais il peut être déplacé. Le déploiement enregistre aussi le digest immuable. Le registry conserve l’identité du pipeline, le commit, l’heure, le rapport de dépendances et la décision de scan. Le support peut ainsi répondre à « qu’exécute ce replica ? » sans inférer depuis un nom comme latest ou stable.

La signature ne prouve pas que le code est correct ; elle prouve qui a attesté un contenu. La politique reste utile si la clé, l’identité du runner et les droits de promotion sont séparés. Un compte capable de modifier le dépôt, le pipeline et la production annule l’intérêt de plusieurs contrôles successifs.

Borner les vulnérabilités acceptées

Un scan trouve des paquets sans contexte d’exploitation. L’équipe attribue chaque exception, fixe une échéance et reconstruit après correction. Une vulnérabilité critique exploitable dans le runtime bloque ; un outil présent seulement dans un stage non publié reçoit une analyse différente. La règle est versionnée pour éviter les dérogations orales.

Aligner santé, logs et arrêt propre

Le healthcheck confirme que le processus peut servir son rôle immédiat ; il ne doit ni muter la base ni appeler toutes les dépendances. Une sonde de disponibilité peut tolérer un partenaire en panne tandis qu’une readiness retire le replica si son propre état l’empêche de répondre. Les métriques métier complètent ces signaux sans transformer la sonde en parcours complet.

Les logs portent version, digest, environnement, corrélation et résultat, jamais le secret injecté. Le tableau de run distingue crash de processus, erreur applicative et dépendance lente. Par exemple, trois redémarrages en dix minutes déclenchent localement une investigation et le gel de la promotion : l’automatisation limite l’indisponibilité mais ne ferme pas la cause ni l’effet sur les messages.

Rendre le rollback observable

Le repli déploie un digest connu, vérifie la compatibilité du schéma et suit erreurs, file et statuts métier. Il ne se réduit pas à relancer le tag précédent. Le journal conserve auteur, raison et version afin que la post-mortem distingue défaut du code, configuration erronée et limite de capacité.

Savoir quand cette discipline devient rentable

Elle devient prioritaire dès que plusieurs développeurs, une CI et au moins un environnement exploité doivent partager un runtime, ou lorsque les extensions système rendent les postes difficiles à aligner. Elle est particulièrement utile aux applications longues, avec workers, assets compilés et exigences de reprise.

Un petit site statique ou une application maintenue par une seule personne sur une plateforme qui gère déjà le build peut rester plus simple sans Docker local. Le critère n’est pas la modernité de l’outil, mais la réduction mesurée des écarts, du temps d’onboarding et des incidents de livraison.

Éviter les fausses parités entre environnements

Reconstruire à chaque promotion

Le même commit peut résoudre une base ou une dépendance différente. Il faut construire une fois, tester puis promouvoir le digest. La configuration reste externe et validée séparément.

Masquer l’application avec un bind mount

Le local fonctionne parce que le répertoire hôte remplace ce que le Dockerfile a oublié de copier. La CI doit lancer l’image sans montage et vérifier la présence des fichiers nécessaires avant publication.

Installer ou migrer au démarrage

Un replica télécharge des paquets ou modifie le schéma pendant que les autres servent du trafic. Le démarrage devient non déterministe. Build, migration et runtime reçoivent chacun une étape et une responsabilité distinctes.

Décider ce qui est commun, dérivé ou interdit

La matrice place chaque élément dans trois colonnes. Sont communs le runtime, les dépendances, le code et les artefacts. Sont dérivés les outils dev, la commande exécutée, la capacité et l’exposition. Sont interdits les secrets persistés, les corrections manuelles, les tags ambigus et les téléchargements au démarrage.

Le comité choisit ensuite partager, spécialiser ou retirer. Une différence reste autorisée si elle répond à un besoin vérifié et possède un test. Le coût complet inclut maintenance du Dockerfile, temps de build, consommation locale, investigation et patching. Une parité qui double chaque boucle de développement n’est pas gratuite.

  1. D’abord, conserver une base commune lorsque la différence ne concerne que l’outillage ou la commande.
  2. Ensuite, dériver un target lorsque des paquets sont nécessaires à une phase mais interdits au runtime.
  3. Puis, séparer une image si le profil de sécurité ou le cycle de publication devient réellement indépendant.
  4. Enfin, refuser toute variante qui ne peut pas être reliée à un besoin, un owner et une preuve.

Plan d’action sur six semaines

Semaines une et deux : inventorier la dérive

L’équipe relève images, bases, extensions, commandes, montages, secrets et différences entre poste, CI, préproduction et production. Elle choisit un flux représentatif : requête web, message asynchrone et build d’assets. Chaque divergence reçoit un motif supposé, un propriétaire et le coût d’un incident récent.

Semaines trois et quatre : construire la chaîne commune

Le Dockerfile multi-stage produit targets dev, test et runtime depuis une base maîtrisée. Les fichiers de verrouillage sont vérifiés, les secrets de build utilisent un montage temporaire et la CI publie un digest après les tests de l’image finale. La configuration de démarrage est validée sans être intégrée aux couches.

Semaine cinq : répéter promotion et repli

La préproduction reçoit le digest exact, exécute la migration compatible et ouvre un périmètre limité. L’équipe retire un secret, interrompt un worker et remplace un replica. Le support retrouve version, logs et statut puis revient au digest précédent sans reconstruire. Tout geste oral rejoint le runbook.

Semaine six : décider l’extension

Le dossier compare temps de build, boucle locale, échecs de déploiement, outils présents et délai de rollback. Il attribue patching, registry, configuration, monitoring et reprise. Le comité élargit si chaque environnement explique ses différences et si la production exécute bien l’artefact certifié.

La sortie contient Dockerfile, matrice d’écarts, inventaire de dépendances, politique de tag et digest, contrôles CI, seuils et rollback. Elle nomme aussi les compromis : un target de développement plus lourd peut être accepté si le runtime reste minimal et si sa maintenance possède un budget.

Le premier mois de production suit les reconstructions évitées, le temps perdu sur l’environnement et les incidents de configuration. Ces mesures ne prouvent pas une productivité universelle ; elles vérifient que la chaîne commune réduit réellement les ambiguïtés du projet. En cas de régression, le périmètre revient au flux pilote.

  1. D’abord, valider le runtime final sans bind mount ni outil de debug.
  2. Ensuite, relier le digest aux tests, à la configuration et au responsable de promotion.
  3. Puis, rejouer l’arrêt d’un worker et le rollback avec les opérations.
  4. Enfin, élargir seulement lorsque poste, CI et production expliquent chaque différence autorisée.

Guides complémentaires pour fiabiliser la livraison

Choisir le niveau d’orchestration

Comparer Docker Compose, Kubernetes et plateforme managée aide à ne pas confondre qualité de l’image et complexité de l’infrastructure qui l’exécute.

Organiser les différents processus

Dockeriser web, queues, cron et workers prolonge le contrat commun lorsque plusieurs commandes partagent le même code.

Sécuriser les changements de socle

Migrer Symfony sans casser le run relie compatibilité applicative, images, migrations et repli.

  • Prouver d’abord que la CI teste le runtime final sans montage de code.
  • Relier ensuite chaque déploiement à un digest et à ses résultats.
  • Réviser enfin les variantes quand leur besoin ou leur propriétaire disparaît.

Conclusion : promouvoir une preuve, pas une reconstruction

Développement, CI et production n’ont pas la même mission. Ils doivent pourtant exécuter le même contrat logiciel : runtime, dépendances, code et artefacts issus d’une construction maîtrisée.

Les différences deviennent saines quand elles sont nommées et testées. Outils de debug, montages, ressources et exposition peuvent varier ; secrets dans les couches, migrations concurrentes et reconstructions après validation restent refusés.

La preuve décisive est un digest promu, observable et réversible. Si le support retrouve sa provenance et peut revenir à la version précédente sans reconstruire, la conteneurisation réduit réellement le risque de livraison.

Dawap peut auditer la chaîne actuelle, fermer les écarts et répéter une promotion avec son rollback dans le cadre d’un accompagnement en développement web sur mesure conçu pour rendre le run reproductible sans sacrifier l’expérience locale.

Portrait de Jérémy Chomel

Vous avez un projet de
développement sur mesure ?

Dawap transforme ce besoin en périmètre livrable, architecture maintenable et trajectoire de mise en production adaptée à vos contraintes.

Besoin d’échanger sur votre projet ? Planifier un rendez-vous

Articles recommandés

Observabilité fonctionnelle d’un workflow métier de bout en bout Développement web Observabilité d’un workflow métier : voir le dossier réel Lire l'article
  • 17 juillet 2026
  • Lecture ~17 min

Logs techniques et disponibilité ne suffisent pas. Instrumentez états, transitions, décisions, délais et reprises pour expliquer où un dossier métier s’est réellement bloqué. Le guide relie événements fonctionnels, traces, métriques, alertes et modes opératoires sans transformer les données personnelles en identifiants de corrélation.

Stratégie de test d’un workflow métier à nombreuses exceptions Développement web Tester un workflow complexe sans explosion combinatoire Lire l'article
  • 17 juillet 2026
  • Lecture ~17 min

Testez les états, transitions, invariants, droits, données et reprises qui portent le risque réel, au lieu de multiplier des scénarios impossibles à maintenir. Cette méthode construit une couverture défendable, injecte les pannes utiles et vérifie aussi les compensations, la concurrence et les preuves attendues par le métier.

Migration progressive d’une application Symfony sans interruption du run Développement web Migration Symfony : monter de version sans casser le run Lire l'article
  • 16 juillet 2026
  • Lecture ~14 min

Une montée de version Symfony touche PHP, dépendances, configuration, données, sessions, cache, Messenger, crons et contrats API. Ce guide propose une trajectoire progressive, une baseline de tests, des critères de retour arrière et une matrice go ou no-go pour moderniser l’application sans confondre migration du framework et refonte métier.

Performance et monitoring d’une application métier Développement web Performance et monitoring d’une application métier Lire l'article
  • 20 janvier 2025
  • Lecture ~45 min

La performance d’une application métier se juge sur la tâche accomplie, pas sur une moyenne globale. Reliez latence, erreurs, saturation et signaux métier, puis définissez les alertes qui déclenchent une action. Traces, métriques et journaux deviennent alors un outil de diagnostic, de dégradation maîtrisée et de reprise.