Une erreur fréquente en débogage de Docker Compose survient lorsqu’on croit avoir défini correctement une variable d’environnement via un fichier env_file, mais que le conteneur démarre avec une valeur différente. Autrement dit, le modèle Compose rendu ne transmet pas nécessairement la même valeur à l’application en exécution. Cet article détaille comment séparer clairement les deux phases, interpolation du modèle et environnement du conteneur, pour déterminer la source réelle de la valeur appliquée. Nous gardons l’étude dans le contexte contrôlé d’une application factice (probe) qui affiche en JSON sa variable APP_MODE. Il ne s’agit ni d’un nouveau bug ni d’une fuite de secret, mais d’une méprise sur la hiérarchie des sources Docker Compose.
La philosophie est la même que dans les concepts éprouvés des container and DevOps toolchain foundations : on doit vérifier chaque étape (variables d’entrée, modèle rendu et processus en cours) pour établir la provenance de la valeur finale. Les sections suivantes décrivent pas à pas la configuration du répertoire de test, la définition explicite de chaque source possible, puis l’exécution de chaque scénario (cas A à G) en isolant les variables APP_MODE selon leur origine (shell, fichier CLI, fichier du service). À chaque cas, nous recueillons (1) la liste des variables autorisées à l’étape d’interpolation (config --environment), (2) le contenu rendu du service (config --format json), et (3) la sortie JSON du conteneur pour comparer la « valeur attendue » et la « valeur observée ». Au terme, un tableau décisionnel ACCEPT/REPAIR/HOLD/RECREATE lie chaque verdict aux preuves recueillies. Le contexte reste strictement local : aucune publication de ports ou de données réelles, juste trois sentinelles « from-shell », « from-cli-env » et « from-service-env ». L’exercice prouve que le mauvais APP_MODE observé n’est pas un défaut caché de Compose, mais la conséquence des règles officielles. Les solutions varient : accepter la configuration observée, corriger la source appropriée, ou relancer tout le service sous un état inconnu. Chacun possède une « provenance » et un « contrat » précis.
Séparez interpolation de modèle et environnement du conteneur
Dans Docker Compose, définir une variable pour interpoler le fichier compose.yaml est une chose, et la faire recevoir par le processus lancé en est une autre. Le fichier compose.yaml effectue d’abord l’interpolation des variables au moment de la création du modèle de configuration, puis ce modèle configure l’environnement du conteneur. Par exemple, ${APP_MODE:-fallback} dans le fichier rend APP_MODE par défaut à "fallback" si rien n’est fourni par le shell ou par un fichier d’interpolation. Mais ce même ${APP_MODE} est ensuite placé sous l’attribut environment du service, ce qui signifie que la valeur insérée dans le modèle définit ce que le conteneur voit. En d’autres termes, les variables statiques du service (via env_file: ou environment:) ne fusionnent pas automatiquement avec les variables d’interpolation. On peut parfaitement voir dans le modèle « contenu attendu du fichier » tandis qu’au runtime le conteneur a une autre valeur. Le rôle de cette section est de clarifier ces deux étapes séparées, pour que l’on comprenne pourquoi « le fichier contient la bonne valeur » n’implique pas que « le processus reçoit la bonne valeur ». (Cela s’inspire du principe de la chaîne de responsabilité DevOps : chaque maillon (fichier, CLI, exécution) doit être vérifié individuellement.)
Le manifeste de configuration est construit selon les règles officielles : l’interpolation priorise d’abord les variables exportées dans l’environnement shell, puis celles du fichier passé à --env-file, puis (en l’absence de ce paramètre) celles du fichier .env du projet. Notez que le fichier listé dans env_file: pour le conteneur n’est pas utilisé pour l’interpolation du modèle. Il sert uniquement à fournir des variables au conteneur après la construction du modèle. Comme nous le verrons, c’est la source principale de confusion : un .env de service (par exemple runtime.env) n’est pas consulté quand Compose réalise ${APP_MODE:-fallback}. On remédie à cette confusion en comparant ce modèle interpolé au runtime effectif.
Figez le répertoire, le contexte et la sonde inerte
Nous travaillons dans un répertoire isolé, sans héritage d’autre .env ni de fichiers cachés. L’arborescence de base est :
project-dir/
├ composeA.yaml
├ composeD.yaml
├ composeE.yaml
├ composeF.yaml
├ runtime.env
├ interpolation.env
• composeA.yaml (fichier principal) : contient le service probe, avec interpolation ${APP_MODE:-fallback}.
• composeD.yaml, composeE.yaml, composeF.yaml : variantes complètes pour les cas D, E, F décrits plus bas.
• runtime.env : fichier de service sous env_file: avec APP_MODE=from-service-env.
• interpolation.env : fichier CLI d’interpolation (modifiable selon cas A à C).
On se place dans le project-dir (on pourra forcer le contexte avec --project-directory .). Nous paramétrons l’image Python probe à utiliser. Par souci de traçabilité, nous résolvons le digest réel de python:3.13-slim avant l’expérience, pour ne pas utiliser une balise mutable. Par exemple, après docker pull python:3.13-slim, on trouve le digest obtenu :
PROBE_IMAGE=docker.io/library/python@sha256:bb2988715db2cf7ace7b53f38f3cffbef7c7046a656bee66245eb0ed386e2e81
Le Compose final utilise image: ${PROBE_IMAGE}. On notera et utilisera cette référence immuable. Nous activons le CLI Compose v2 (par exemple via docker compose ou docker compose plugin). Voici quelques commandes de préparation (dans le répertoire de travail) :
# Créer runtime.env avec la valeur de service
echo "APP_MODE=from-service-env" > runtime.env
# Créer un .env CLI (vide au départ)
> interpolation.env # fichier CLI d'interpolation (vide pour l'instant)
# Résoudre et exporter le digest approuvé de l'image de sonde
docker pull python:3.13-slim
PROBE_IMAGE=$(docker inspect --format='{{index .RepoDigests 0}}' python:3.13-slim)
export PROBE_IMAGE
echo "Utiliser PROBE_IMAGE=$PROBE_IMAGE"Pour chaque commande Compose, nous fixons -f et --project-directory . afin d’être explicites, et -p p pour isoler le projet (-p proj par exemple). Nous enregistrerons la version Compose (docker compose version) et la version du daemon Docker pour la reproductibilité. Les commandes clés utilisées sont :
• docker compose --project-directory . -f <compose-file> config --environment (liste les variables d’interpolation autorisées).
• docker compose --project-directory . -f <compose-file> config --format json (affiche le modèle résolu en JSON).
• docker compose --project-directory . -f <compose-file> run --rm probe (exécute la sonde probe).
On n’expose aucun port réseau (network_mode: none est déjà fixé) et on garde le contexte Docker minimal. Aucune donnée sensible n’est utilisée, seulement les valeurs sentinelles from-shell, etc. La sortie de chaque étape est capturée, mais nous ne publierons que les champs essentiels des journaux.
Enregistrez seulement les entrées de configuration autorisées
Avant chaque cas, on définit comment APP_MODE est fourni à Compose. Nous n’utilisons jamais docker compose run -e (premier niveau de priorité, sinon c’est un scénario différent). Les seules sources sont : l’environnement shell (variable exportée avant l’appel Compose), le fichier CLI --env-file (interpolation.env), et la valeur par défaut ${APP_MODE:-fallback}. Le fichier runtime.env (sous env_file: dans le service) n’intervient pas ici pour l’interpolation.
Pour chaque exécution, on relève d’abord la sortie de :
docker compose -f composeX.yaml --project-directory . --env-file interpolation.env config --environment
Cette commande liste toutes les variables que Compose utilise pour l’interpolation du modèle. Le résultat peut inclure APP_MODE et d’autres variables (par exemple PROBE_IMAGE figure en interpolation). Afin de ne garder que les informations pertinentes, on filtre par exemple :
docker compose -f composeA.yaml --project-directory . --env-file interpolation.env config --environment | grep -E '^(APP_MODE|PROBE_IMAGE)='
Ainsi on obtient, cas par cas, la valeur d’APP_MODE utilisée pour construire le modèle. Ensuite, on exécute :
docker compose -f composeX.yaml --project-directory . --env-file interpolation.env config --format json > model.json
Le fichier JSON généré contient le modèle complet. La clé "services" → "probe" → "environment" révèle quelle valeur est effectivement passée dans l’environnement du conteneur. Enfin :
docker compose -f composeX.yaml --project-directory . --env-file interpolation.env run --rm probe
Cette commande imprime un JSON avec {"APP_MODE": ...} selon le conteneur lancé. Nous comparons cette valeur à l’valeur attendue préalablement déclarée.
En résumé, pour chaque cas, nous collectons et déclarons sous forme de tableau de référence l’état des trois sources (shell, fichier CLI, fichier runtime.env), l’expression Compose, la valeur attendue et la valeur réellement obtenue. Toute trace de variable hors de ces sentinelles est ignorée. Par exemple, PROBE_IMAGE sert uniquement à vérifier le digest; on ne le cite pas comme « preuve », juste pour contrôle technique. Les retours de code (exit code) sont notés pour distinguer succès de validation (0) et échec (no-lancement). Un service qui n’a pas été lancé par manque de variable requise est noté « non exécuté », pas « chaîne vide ».
Écrivez un manifeste source-et-valeurs indépendant
Avant d’exécuter, définissons de manière précise l’« oracle » des résultats pour chaque cas. Le tableau ci-dessous liste pour chaque cas A–G l’état de APP_MODE dans l’environnement shell, dans le fichier CLI interpolation.env, dans runtime.env, l’expression Compose utilisée, et enfin la valeur attendue dans le conteneur (avant d’exécuter la commande). L’expression Compose dans environment: est soit ${APP_MODE:-fallback}, soit ${APP_MODE:?error}, ou nulle si supprimée. Les états distincts « absent », « vide » et « non lancé » sont traités séparément.
Cas | Shell | interpolation.env | runtime.env | Expression Compose | Valeur attendue |
A | (non défini) | (APP_MODE absent ou vide) | APP_MODE= | ${APP_MODE:-fallback} | fallback (par défaut) |
B | (non défini) | APP_MODE= | APP_MODE= | ${APP_MODE:-fallback} | from-cli-env |
C | APP_MODE= | APP_MODE= | APP_MODE= | ${APP_MODE:-fallback} | from-shell |
D | (non défini) | APP_MODE= | APP_MODE= | (environment supprimé) | from-service-env |
E | (non défini) | APP_MODE= | APP_MODE= | APP_MODE: "" | "" (vide explicite) |
F | (non défini) | (non défini ou vide) | APP_MODE= | ${APP_MODE:?error} | erreur interpolation (pas lancé) |
G | (non défini) | APP_MODE= | APP_MODE= | ${APP_MODE:?error} | from-cli-env |
• Détails : le cas D utilise un fichier complet composeD.yaml où l’entrée environment: APP_MODE: ... est supprimée. La seule source valable est alors env_file: runtime.env, donc on attend from-service-env. Le fait que interpolation.env comporte APP_MODE=from-cli-env n’injecte rien dans le conteneur sans bloc environment actif.
• Cas E met environment: APP_MODE: "" (chaîne vide explicite) dans composeE.yaml. Même si runtime.env contient from-service-env, la valeur vide l’emporte, selon la règle que tout attribut environment l’emporte sur env_file. On attend donc une chaîne vide.
• Les cas F/G utilisent ${APP_MODE:?APP_MODE must be supplied} comme expression. Par définition, ce format renvoie l’erreur si APP_MODE est absent ou vide. Ainsi F (pas de valeur fournie) déclenche une erreur de construction du modèle (aucun conteneur lancé). G valide ce modèle car APP_MODE est bien défini (from-cli-env).
• Le mécanisme de repli ${VAR:-fallback} choisi ici assure que on utilise fallback même lorsque la variable est vide. (C’est ce qui fait la différence entre ${VAR:-default} et ${VAR-default} : le :- déclenche le défaut si vide ou absent, le seul - n’intervient qu’à l’absence.) Nous y reviendrons dans la section « Choix des valeurs par défaut ».
Cette table constitue notre contrat de configuration. Chaque observation (« valeur dans conteneur ») sera comparée à la colonne « Valeur attendue » pour décider du verdict. Notez que tous les cas sauf F/G démarrent un conteneur sain (code 0). Simplement, certaines valeurs sont « incorrectes » par rapport au contrat.
Reproduisez le fallback qui écrase env_file
Cas A : sans valeur shell ni CLI, et avec l’expression ${APP_MODE:-fallback}, on attend la valeur par défaut fallback. Même si runtime.env propose from-service-env, le fichier composeA.yaml est conçu pour tomber sur fallback. Exécutons :
# Cas A : interpolation.env vide, shell sans APP_MODE
unset APP_MODE
> interpolation.env # on s’assure qu'il n'y a aucune ligne APP_MODE
# Liste des variables d'interpolation
docker compose -f composeA.yaml -p p --project-directory . config --environment
# Sortie attendue (extrait) : APP_MODE=fallbackOn constate que config --environment montre APP_MODE=fallback, indiquant que c’est cette valeur qui a été interpolée. Ensuite :# Modèle Compose en JSON
docker compose -f composeA.yaml -p p --project-directory . config --format jsonLe JSON rendu contient sous services.probe.environment :"environment": {
"APP_MODE": "fallback"
}C’est la preuve que Compose a inséré APP_MODE: "fallback" dans le modèle final. Enfin :
docker compose -f composeA.yaml -p p --project-directory . run --rm probe
Cette commande affiche {"APP_MODE": "fallback"}. Le conteneur a effectivement reçu "fallback". Cette valeur correspond à l’expression par défaut ${APP_MODE:-fallback}.
L’explication est que lors de la construction du modèle, l’expression sans variable définie produit fallback. Ce résultat devient l’entrée APP_MODE du service, et selon la prééminence de l’attribut environment sur env_file, cette entrée l’emporte sur la valeur from-service-env fournie dans runtime.env. Ce comportement est normal et documenté : le fichier de service (env_file) ne peut remplacer une variable déjà explicitement définie dans environment.
Ne pas utiliser env_file de service pour l’interpolation
Notez le point clé : la valeur de runtime.env n’est jamais lue à l’étape d’interpolation. Docker Compose distingue sources d’interpolation (shell, CLI --env-file, .env local) et sources d’environnement du conteneur (attributs environment ou env_file). Dans notre cas, runtime.env n’est consulté que lorsqu’il remplit l’environnement du conteneur. Ainsi, même si runtime.env contient APP_MODE=from-service-env, il n’apparaîtra pas dans config --environment. Ce fichier de service n’intervient pas comme source d’interpolation.
En résumé, le cas A confirme que la variable a été interpolée et transmise comme attendu (fallback). Ce n’est pas un bug de Docker Compose, mais l’effet combiné de ${APP_MODE:-fallback} et de la règle de priorité des attributs. L’erreur aurait été de supposer que env_file servirait de source d’interpolation.
Tracez l’entrée CLI approuvée dans le processus
Cas B : maintenant interpolation.env définit APP_MODE=from-cli-env et le shell ne définit pas la variable. Docker Compose utilise alors la valeur du fichier CLI, qui a le deuxième niveau de priorité après le shell. Il remplace le fallback par from-cli-env. On vérifie :
unset APP_MODE
echo "APP_MODE=from-cli-env" > interpolation.env
docker compose -f composeA.yaml -p p --project-directory . config --environment
La sortie attendue est APP_MODE=from-cli-env. Le modèle JSON montre :
"environment": {
"APP_MODE": "from-cli-env"
}
Et docker compose run probe affiche {"APP_MODE": "from-cli-env"}. Le fichier CLI a fourni la valeur et celle-ci a été injectée dans le modèle rendu (puis au conteneur). Nous voyons bien que la variable d’interpolation acceptée (liste des variables utiles) correspond à la ligne du fichier CLI, pas à runtime.env. Ce comportement suit la documentation : la valeur dans --env-file en CLI surpasse l’expression par défaut.
Ce cas illustre aussi la nécessité de capturer à la fois l’entrée CLI et la sortie du modèle. On a le droit d’inspecter interpolation.env, mais sans cette exécution on ne saurait pas si l’appel réel a utilisé ce fichier. La commande config --environment certifie que le contexte de lancement a bien le fichier souhaité.
Dévoilez une surcharge d’environnement shell ambiante
Cas C : ici on laisse interpolation.env avec APP_MODE=from-cli-env, mais on ajoute dans le shell export APP_MODE=from-shell. Selon les règles de priorité, l’environnement shell l’emporte sur le fichier CLI. Autrement dit, l’application Compose verra APP_MODE=from-shell à la construction du modèle. Testons cela :
export APP_MODE=from-shell
docker compose -f composeA.yaml -p p --project-directory . config --environment
On s’attend à APP_MODE=from-shell listé. Le modèle (config --format json) montre "APP_MODE": "from-shell". Finalement docker compose run probe renvoie {"APP_MODE": "from-shell"}. Ainsi c’est la valeur du shell qui a été interpolée, remplaçant celle du fichier CLI. Ce cas montre qu’un simple examen du fichier CLI n’est pas suffisant : un pilote d’appel (bash, CI/CD, etc.) peut aussi injecter des variables au moment de l’appel. C’est pourquoi nous capturons une seule fois l’environnement effectif avant le lancement, et nous le maintenons.
Faites correspondre le contexte inspecté au contexte lancé
Pour garantir la cohérence, on a réutilisé exactement la même syntaxe de commande et les mêmes variables d’environnement pour les deux appels config --environment et run probe. Toute différence (comme lancer le config sous une session et le run sous une autre) risquerait de décaler le contexte. Ici, l’observation montre clairement que la surcharge de variable issue du shell remporte la priorité, comme prévu. Un outil comme un tableau de bord d’automatisation pourrait ne rapporter que la valeur attendue (config), masquant ce genre de surcharge.
Comparez les configurations « runtime uniquement » et « vide explicite »
Cas D : on utilise un autre fichier complet composeD.yaml, identique à composeA.yaml sauf que la section environment: est supprimée. Ainsi, aucune interpolation de ${APP_MODE} n’est faite du tout. Le seul emplacement pour fournir APP_MODE est runtime.env via env_file. Nous laissons toutefois interpolation.env inchangé (APP_MODE=from-cli-env), pour tester l’effet. En exécutant :
unset APP_MODE
echo "APP_MODE=from-cli-env" > interpolation.env
docker compose -f composeD.yaml -p p --project-directory . config --environment
docker compose -f composeD.yaml -p p --project-directory . run --rm probe
On trouve que l’entrée d’interpolation ne comporte plus APP_MODE (puisqu’on n’utilise pas ${APP_MODE} dans Compose). Le modèle (config --format json) affiche :
"environment": {
"APP_MODE": "from-service-env"
}
Le conteneur renvoie {"APP_MODE": "from-service-env"}. Sans bloc environment, le fichier de service runtime.env a alimenté l’environnement du conteneur. Ce cas souligne deux points : (a) le fichier CLI seul (interpolation.env) ne crée pas une variable dans le conteneur en l’absence d’expression Compose correspondante, (b) supprimer la variable de l’environnement de service l’autorise à venir de l’env_file. La valeur from-service-env est donc la seule « source de vérité ».
Cas E : on élabore un autre fichier complet composeE.yaml, où l’on définit explicitement dans environment: APP_MODE: "" (une chaîne vide explicite en YAML), tout en conservant runtime.env. Nous exécutons :
unset APP_MODE
echo "APP_MODE=from-cli-env" > interpolation.env
docker compose -f composeE.yaml -p p --project-directory . config --environment
docker compose -f composeE.yaml -p p --project-directory . run --rm probe
Ici, config --environment donne APP_MODE= (vide). Le modèle JSON montre "APP_MODE": "" et le conteneur renvoie {"APP_MODE": ""}. La chaîne vide explicite l’emporte sur from-service-env de env_file, conformément à la hiérarchie des attributs (attribut environment l’emporte toujours sur env_file, même si la valeur est vide). Le contrat le plus précis est ainsi appliqué : l’application reçoit bien une chaîne vide, ce qui doit être interprété différemment de « pas de variable ». Dans les deux cas D et E, nous avons pu observer la différence entre absence d’entrée et entrée présente avec valeur vide.
Exigez une valeur quand le fallback n’est pas permis
Cas F : on modifie composeF.yaml pour utiliser ${APP_MODE:?APP_MODE must be supplied} au lieu de :-. Cette syntaxe signifie qu’APP_MODE doit être défini et non vide, sinon Compose interrompra la construction avec une erreur. Sans valeur définie dans le shell ni dans le fichier CLI, on a :
unset APP_MODE
> interpolation.env # pas de ligne APP_MODE
docker compose -f composeF.yaml -p p --project-directory . config --quiet
On s’attend à ce que config échoue (code d’erreur). En effet, Compose renvoie une erreur du type "APP_MODE must be set and non-empty" et aucun conteneur n’est lancé. L’interpolation ne trouve pas de valeur satisfaisant ${APP_MODE:?}, donc la construction du modèle est stoppée. Même si runtime.env contient une valeur, elle ne peut pas compenser ici, car les fichiers de service ne sont pas pris en compte par ${?}. (Le rôle de ${?} est de valider l’entrée contractuelle, pas de fournir une valeur manquante.)
Cas G : même fichier composeF.yaml mais avec interpolation.env défini :
echo "APP_MODE=from-cli-env" > interpolation.env
docker compose -f composeF.yaml -p p --project-directory . config --environment
docker compose -f composeF.yaml -p p --project-directory . run --rm probe
Cette fois, config --environment listera APP_MODE=from-cli-env, et le modèle JSON contient "APP_MODE": "from-cli-env". Le conteneur imprime {"APP_MODE": "from-cli-env"}. Comme prévu, fournir la valeur requise empêche l’erreur de construction.
Choisissez délibérément les valeurs par défaut
Ces tests montrent la différence entre les opérateurs ${VAR:-default} et ${VAR-default}. Rappelons-le : ${VAR:-default} renvoie default si VAR est défini mais vide ou s’il est simplement absent, alors que ${VAR-default} ne renvoie default que si VAR est complètement indéfini. Dans notre politique d’application, nous avons décidé que la variable APP_MODE ne devait jamais être vide par défaut. C’est pourquoi nous avons utilisé :-fallback. Si au contraire on avait toléré la valeur vide comme signifiant « désactivé », l’autre syntaxe ${APP_MODE-fallback} aurait évité d’émettre l’erreur mais aurait préservé from-service-env si APP_MODE= existait dans le shell. Chaque cas d’usage devra décider s’il veut accepter ou non le vide. Docker ne le fait pas pour nous : il se contente d’appliquer la règle choisie.
Choisissez un seul contrat de source de configuration à réparer
Face à ces résultats, deux contrats récurrents peuvent être adoptés :
• Contrat A (env_file maître) : supprimer toute entrée environment: APP_MODE dans le fichier Compose. On confie entièrement APP_MODE au fichier runtime.env. Dans ce cas, une variable CLI/interpolée ne peut pas le surcharger. Ce contrat convient si l’équipe veut centraliser la valeur dans un fichier répertorié, et considère le lancement comme l’unique point de configuration.
• Contrat B (interpolation explicite) : conserver une entrée d’environnement dans le fichier Compose, soit avec un défaut explicite, soit requise. Le script de lancement (shell/CI) doit toujours passer APP_MODE, éliminant ainsi les ambiguïtés. Le fichier runtime.env sert alors seulement de secours historique ou de documentation.
Il n’existe pas de solution « universellement meilleure » : cela dépend de qui est responsable de la valeur APP_MODE et de la fréquence des changements. Quoi qu’il en soit, on versionne ce contrat choisi (par exemple via Git) de sorte que tout changement futur soit traçable. L’essentiel est que la source unique de vérité soit définie et appliquée sans surprise. Si on adopte le Contrat A, on veillera à restaurer un fichier de service autoritaire. Pour le Contrat B, on verrouillera au moins une méthode (CLI ou shell) pour fournir la variable, et on documentera strictement son usage.
Capturez les trois étapes de preuve dans un seul journal de déploiement
Pour que cette vérification soit traçable, il faut enregistrer conjointement : les révisions des fichiers sources (compose, .env CLI, runtime.env), le sous-ensemble de variables d’interpolation autorisées (config --environment), le contenu du modèle rendu (config --format json), le contexte d’appel exact (options CLI, version de Compose, digest d’image), et enfin la sortie du conteneur sonde. Par exemple, un enregistrement de preuve pourrait ressembler à ce tableau (valeurs attendues et observées) :
Étape | Valeur | Observé (JSON) | Verdict |
APP_MODE shell | (défini ou non) | (idem) | |
APP_MODE CLI | (contenu d’interpolation.env) | (idem) | |
APP_MODE runtime.env | from-service-env | (idem) | |
Modèle (config) | (champ JSON) | APP_MODE: "..." | |
Conteneur (run) | {"APP_MODE": "..."} | {"APP_MODE": "..."} |
Chaque ligne de ce journal lie une source au résultat. Par exemple, dans le cas C on aurait shell=from-shell, cli=from-cli-env, runtime=from-service-env, modèle APP_MODE: "from-shell", conteneur {"APP_MODE":"from-shell"}, d’où un ACCEPT. Il est crucial de séparer la configuration déclarée du résultat du conteneur. La colonne « modèle » (JSON) n’est que la prévision ; seul le conteneur confirmé fait foi pour la sortie finale. Ceci évite de dédouaner Compose en se basant uniquement sur config.
Ne pas déduire l’état runtime d’un aperçu de config
Un piège classique est de croire que si docker compose config montre la bonne valeur, alors le conteneur fera de même. Ce n’est vrai que si l’interpolation a utilisé la bonne source. Par exemple, dans le cas C on pourrait vérifier config sous une session et ne pas voir la surcharge du shell. C’est pourquoi nous maintenons ces preuves encapsulées : le même appel qui a généré le modèle est celui qui lance le conteneur.
Distinguez santé et succès d’exécution de l’acceptation de valeur
Toutes nos sondes sont de simples scripts Python qui « réussissent » (exit code 0) tant qu’ils peuvent imprimer JSON. Un 0 ne garantit rien quant au contenu. Par conséquent, nous vérifions explicitement le champ APP_MODE dans le JSON de sortie. On peut même faire un test négatif volontaire pour prouver qu’un succès général de conteneur ne suffit pas. Par exemple, exécuter :
result=$(docker compose -f composeE.yaml -p p --project-directory . run --rm probe)
if [ "$(echo $result | jq -r .APP_MODE)" != "expected-value" ]; then
echo "Erreur: la valeur APP_MODE est inattendue"
fi
Ici composeE sort une chaîne vide. Un outil de supervision basique signalerait une application « healthy » puisque le conteneur a bien tourné et retourné 0. Mais pour nous ce n’est pas la bonne valeur. C’est pour cela qu’on distingue santé de conteneur et vérification du contrat. Ce constat rejoint l’idée qu’un simple outil de surveillance de la disponibilité ne remplace pas une logique de test unitaire du paramétrage de configuration. D’où l’importance de consigner précisément le résultat de l’application (champs JSON) et de le comparer à l’oracle. L’article du blog Refonte sur monitoring versus configuration evidence souligne cette même séparation de responsabilités entre les métriques et la configuration.
Employez une matrice de décision spécifique à la configuration
À la lumière des preuves acquises, on adopte une matrice décisionnelle adaptée au contexte Compose. Chaque verdict est lié aux preuves et à la propriété de l’élément à corriger :
• ACCEPT (Accepter) : le conteneur affiche exactement la valeur attendue selon le contrat choisi. Les sources sont claires et recensées (par exemple, pour le Contrat A on trouve que runtime.env a produit la valeur définie). On documente la preuve (modèle + sortie) et on clôt l’incident.
• REPAIR (Réparer) : la valeur du conteneur est incorrecte par rapport au contrat. Il faut modifier le point de configuration approprié, sans redémarrer en boucle. Par exemple, si on décide que APP_MODE ne doit pas être vide, on change l’expression en ${APP_MODE:?} ou on alimente le shell/CLI. Le propriétaire du fichier Compose ou du script de lancement doit appliquer la correction.
• HOLD (En attente) : les entrées sont manquants ou ambigus. Par exemple, APP_MODE n’est nulle part fourni alors qu’il est requis. Cela nécessite une action (ajout de valeur dans le shell ou le fichier) avant de réappliquer Compose. On retient le déploiement en l’état tant que le point de configuration n’est pas clarifié.
• RECREATE AND REVALIDATE (Recréer et revérifier) : la configuration source a changé (commit Git, par exemple) tandis que le conteneur fonctionne toujours sur une ancienne valeur. Dans ce cas, on redéploie le service (recréation du conteneur) et on effectue à nouveau les tests unitaires. Ce verdict ne consiste pas à « tout arrêter », mais à relancer avec la nouvelle configuration en maintenant la preuve.
Chaque décision est associée à un propriétaire : dans ACCEPT, c’est souvent le propriétaire de la configuration (application) qui vérifie la concordance; en REPAIR ou HOLD, l’ingénieur DevOps ou le développeur applicatif qui gère le déploiement doit intervenir. Notons qu’un échec de test de valeur (cas F non lancé) n’est pas précisément un déploiement raté : on considère plutôt que la mise en config a été bloquée avant lancement, évitant de produire une mise en production incorrecte.
Assignez la propriété et préservez un retour en arrière
Pour chaque verdict, il doit être clair qui prend en charge la résolution : éditeur du fichier Compose (cas de corrections sur environment), ingénieur DevOps qui exécute les commandes, ou mainteneur applicatif (validation de la valeur métier). Lors d’une action REPAIR, on conserve en état la configuration précédente (par exemple en taggant l’image de conteneur ou en archivant les anciens fichiers .yaml). Cela permet de revenir facilement à l’état de fonctionnement connu si besoin.
Après toute correction ou réexécution, on doit relancer les tests positifs et négatifs : refaire un config --environment et run probe pour chaque cas déjà passé. Ainsi, on vérifie qu’un changement n’a pas introduit de nouvelle régression. En particulier, on ne confond pas « recréation d’un conteneur » (docker compose up) avec la restauration de l’état applicatif antérieur. Les tests positifs (valeur correcte) et négatifs (valeur incorrecte) servent de gardiens pour chaque déploiement.
Affinez les bases DevOps derrière ces revues de configuration
L’exercice ci-dessus illustre l’importance des fondations DevOps dans la gestion de configuration et de conteneurs. Cela correspond aux domaines clés enseignés dans le DevOps Engineering Program de Refonte Learning, qui couvre notamment Docker/Compose, l’intégration continue, la configuration gérée et la validation opérationnelle. Ce programme (3 mois à temps partiel) inclut des travaux pratiques et une supervision personnalisée. Pour en savoir plus sur la formation et ses modules (sans engagement de placement automatique, mais avec un certificat de stage à la clef), consultez le programme DevOps Engineering Program. Le traitement rigoureux des variables d’environnement vu ici est typique de l’approche DevOps recommandée en production.
Pour approfondir le sujet, notre blog aborde les container and DevOps toolchain foundations, des controlled system-administration labs pour valider des environnements locaux, des repeatable database environments sur Docker Compose, etc. Ils fournissent le contexte, sans invalider les points spécifiques démontrés ici.
