Je veux faire un sed sur un fichier de conf au démarrage d’un conteneur tiers, avant que son daemon ne lise la conf. Pattern naturel : un entrypoint wrapper qui fait le sed puis ré-exécute la commande originale via $@.
services:
app:
image: vendor/app:latest
entrypoint:
- /bin/sh
- -c
- |
sed "s|PLACEHOLDER|$$VALUE|g" /tmp/conf.tmpl > /opt/conf.xml
exec "$$0" "$$@"
Logique attendue : sh -c reçoit les positional args, $0 et $@ reproduiront le CMD original.
Symptôme
Container démarre, exit 0 en 1 seconde, aucun log applicatif. Pas de stack trace, pas d’erreur. Restart loop avec restart: unless-stopped.
Cause
Inspection de l’image :
$ docker image inspect vendor/app:latest \
--format '{{json .Config.Entrypoint}} | {{json .Config.Cmd}}'
["/opt/app/bin/java"] | ["-jar","server.jar","conf/app.xml"]
L’image a ENTRYPOINT=["java"] et CMD=["-jar","server.jar","conf/app.xml"]. La commande effective au run normal : java -jar server.jar conf/app.xml.
Quand on override entrypoint: dans compose, on remplace ENTRYPOINT de l’image. Seul le CMD reste, devenant les positional args du nouvel entrypoint. Dans le wrapper, "$0" vaut -jar (premier élément du CMD), pas java.
exec "-jar" server.jar conf/app.xml essaie d’exécuter un binaire littéralement nommé -jar. Selon le shell : exit 127, exit 0 silencieux (sh traite -jar comme une option d’exec), ou comportement indéfini. Avec set -e, le résultat varie d’un shell à l’autre.
Le piège est insidieux : on suppose souvent qu’override d’entrypoint = override de CMD = équivalent. C’est faux. Docker stocke ENTRYPOINT et CMD séparément, et entrypoint: en compose ne touche QUE le premier.
Solution
1. Hardcoder le binaire dans le wrapper (le plus simple, légèrement fragile aux mises à jour d’image) :
entrypoint:
- /bin/sh
- -c
- |
sed "s|PLACEHOLDER|$$VALUE|g" /tmp/conf.tmpl > /opt/conf.xml
cd /opt/app
exec /opt/app/bin/java -jar server.jar conf/app.xml
2. Garder $@ correct via un séparateur -- :
entrypoint:
- /bin/sh
- -c
- |
sed "s|PLACEHOLDER|$$VALUE|g" /tmp/conf.tmpl > /opt/conf.xml
exec /opt/app/bin/java "$$@"
- --
Le -- devient $0 (placeholder), le CMD original devient $@. Stable même si le CMD change dans une mise à jour d’image.
3. Build une image custom : FROM vendor/app + COPY entrypoint + ENTRYPOINT [...]. Le plus propre si la modif est durable.
Lessons learned
entrypoint:en compose remplace ENTRYPOINT de l’image, pas seulement CMD : si l’image a un binaire dans ENTRYPOINT (java,python,node…), le wrapper ne le voit pas dans$@docker image inspect --format '{{json .Config.Entrypoint}} | {{json .Config.Cmd}}'révèle en 1 seconde si l’image utilise ENTRYPOINT, CMD, ou les deux- Exit 0 sans log = entrypoint qui a paniqué : ne pas chercher dans les logs de l’app, regarder le COMMAND effectif via
docker inspect <container> --format '{{.Config.Entrypoint}}' - Pour préserver
$@correct dans unsh -cwrapper, ajouter un placeholder--en fin d’array d’entrypoint :$0devient le--et$@reproduit le CMD complet - L’erreur « command not found » sur un argument qui commence par
-est parfois silencieuse (exit 0 selon le shell) parce que le shell traite-jarcomme une option d’exec