Aller au contenu
Sylvain Joffraud l4w
Retour

Bind mount fantôme : git ne remplace pas un dossier

Variante vicieuse d’un piège classique. Docker a auto-créé un dossier vide à la place d’un fichier source manquant (cf [[66-docker-bind-mount-source-fichier-manquant]]). Je corrige la cause initiale, je commit le fichier dans git, je redéploie via un PaaS qui fait un git pull automatique. Et le déploiement échoue encore exactement de la même manière.

Symptôme

Au deploy après le fix :

Error response from daemon: failed to create task ...
not a directory: Are you trying to mount a directory onto a file ...

Le même message qu’au premier deploy. Je vérifie le repo distant, le fichier y est bien. Je SSH sur l’hôte, et là :

$ ls -la /data/app/conf/
drwxr-xr-x 2 root root 4096 traccar.xml         ← dossier vide
drwxr-xr-x 2 root root 4096 traccar.xml.example ← dossier vide

Les fichiers du repo sont devenus (restés) des dossiers vides sur le serveur. Le git pull qu’a fait le PaaS n’a rien changé.

Cause

Une fois que Docker a posé un dossier (même vide) à un chemin, git refuse silencieusement de le remplacer par un fichier régulier lors d’un git pull ou git checkout. Ce n’est pas une faille — c’est une protection : git ne supprime pas un dossier non versionné (potentiellement créé par l’utilisateur) pour y poser un fichier suivi.

Le cycle complet du piège :

  1. Premier deploy : fichier manquant côté repo → Docker auto-crée un dossier vide
  2. Tu corriges, commits le fichier, push
  3. Deploy suivant : git pull voit qu’il faut écrire un fichier X, voit qu’X existe déjà comme dossier non versionné → skip silencieux ou warning facile à manquer
  4. Compose up → même erreur de mount qu’au premier deploy
  5. Tu boucles indéfiniment

Les PaaS qui utilisent git pull (par opposition à un re-clone complet) sont particulièrement exposés. Avec git clean -fdx avant chaque pull le problème disparaît, mais c’est rarement le default.

Solution

Court terme : SSH sur l’hôte cible, rm -rf les dossiers fantômes, relancer le deploy. Marche jusqu’au prochain incident.

Long terme : éliminer le bind mount à la source. Embarquer le fichier dans une image custom :

FROM vendor/app:latest
COPY conf/app.xml /opt/app/conf/app.xml

Plus aucun bind mount → plus de mount-type-mismatch possible → plus de dossier fantôme. C’est la seule mitigation qui ne dépend pas du comportement du wrapper git du PaaS.

Lessons learned

Questions fréquentes

Pourquoi mon git pull ne remplace-t-il pas un dossier par un fichier ?
C’est une protection délibérée de git, pas un bug : git refuse de supprimer un dossier non versionné pour y poser un fichier suivi lors d’un git pull ou git checkout. Il considère que ce dossier a pu être créé par l’utilisateur. Le pull skippe alors silencieusement le fichier, ou émet un warning facile à manquer.
Docker affiche « not a directory: Are you trying to mount a directory onto a file » après un fix, comment diagnostiquer ?
Faites un ls -la sur le chemin monté côté hôte : vous y verrez probablement un dossier vide là où devrait être votre fichier. Docker avait auto-créé ce dossier quand le fichier source manquait, et un git pull ultérieur n’a pas pu l’écraser par le vrai fichier. Le bind mount tombe alors sur un dossier au lieu d’un fichier.
Comment corriger un dossier fantôme créé par Docker à la place d’un fichier bind-monté ?
À court terme, faites un SSH sur l’hôte cible, supprimez les dossiers fantômes avec rm -rf puis relancez le déploiement. À long terme, supprimez le bind mount à la source en embarquant le fichier dans une image custom via COPY conf/app.xml /opt/app/conf/app.xml : plus de mount, plus de mismatch possible.
Pourquoi un PaaS qui fait git pull retombe-t-il dans le piège du dossier fantôme alors qu’un re-clone non ?
Un re-clone complet repart d’un répertoire propre et écrit donc le fichier sans conflit. Le git pull incrémental, lui, rencontre le dossier non versionné déjà présent et le saute silencieusement. Ajouter un git clean -fdx avant le pull casse le piège, mais ce n’est rarement le comportement par défaut des outils CI/CD.

Partager cet article :

Article précédent
Override d’entrypoint : l’image perd son ENTRYPOINT
Article suivant
Docker bind mount : la source absente crée un dossier vide