26 juillet 2026 9 min de lecture

Exploiter de manière fiable des montages Rclone dans Docker

Pour qu’un montage FUSE depuis un conteneur fonctionne aussi sur l’hôte et dans d’autres conteneurs, la propagation des montages, AppArmor et la restauration après incident doivent fonctionner de concert.

Un montage Rclone s’exécute dans un conteneur Docker, mais doit également être disponible sur l’hôte et dans d’autres conteneurs. Pour cela, les événements de montage doivent traverser plusieurs espaces de noms. Une seule option Compose ne suffit pas.

Lors d’un test pratique avec Ubuntu 25.10, le noyau 6.17 et Docker 29.6, trois erreurs indépendantes sont apparues : Docker a discrètement rétrogradé rshared, AppArmor a bloqué fusermount3, et un conteneur consommateur est resté attaché à l’ancien montage après un redémarrage. Le cas d’utilisation concret était un stockage cloud pour Paperless-ngx; les mêmes mécanismes s’appliquent aussi à d’autres outils FUSE tels que sshfs.

1. La source hôte doit elle-même être shared

Pour qu’un montage issu du conteneur atteigne l’hôte, le bind nécessite la propagation rshared :

    volumes:
      - type: bind
        source: /srv/storage/media
        target: /data
        bind:
          propagation: rshared

rshared ne fonctionne que si la source du bind sur l’hôte est elle-même un point de montage avec une propagation shared. Un répertoire ordinaire ne remplit pas cette condition. Docker ne signale néanmoins aucune erreur, mais utilise silencieusement une propagation plus faible. Cela se reconnaît dans /proc/self/mountinfo à l’intérieur du conteneur :

1938 2077 8:2 /srv/storage/media /data rw,relatime master:1 - ext4 /dev/sda2 rw

master:1 signifie une propagation slave : les montages viennent de l’hôte, mais ne repartent jamais vers lui. Il faudrait shared:N. La solution consiste à faire un bind de la source sur elle-même et à le marquer comme shared :

mount --bind /srv/storage/media /srv/storage/media
mount --make-shared /srv/storage/media
Explication des options
OptionEffet
--bind quelle zielLie un répertoire à un second chemin ; ici à lui-même, ce qui fait du répertoire un point de montage indépendant
--make-shared pfadDéfinit la propagation de ce point de montage sur shared, afin que les événements de montage soient transmis dans les deux sens

Pour que cela survive à un redémarrage, il faut l’intégrer dans une unité systemd avec Before=docker.service. Vérification : findmnt -no PROPAGATION /srv/storage/media doit renvoyer shared.

2. AppArmor contrôle également fusermount3 dans le conteneur

Avec une propagation correcte, le problème suivant est apparu. Le montage sur le chemin partagé échouait toujours :

NOTICE: mount helper error: fusermount3: mount failed: Permission denied
CRITICAL: Fatal error: failed to mount FUSE fs: fusermount: exit status 1

Les droits supplémentaires habituels du conteneur n’y ont rien changé : ni CAP_SYS_ADMIN et /dev/fuse, ni unconfined ou même --privileged. Un montage tmpfs fonctionnait vers la même destination, et FUSE fonctionnait sur d’autres chemins. Seul le journal d’audit du noyau a révélé la véritable cause :

audit: type=1400 apparmor="DENIED" operation="mount" class="mount"
  info="failed mntpnt match" error=-13 profile="fusermount3"
  name="/data/documents/originals/" fstype="fuse.rclone"

Ubuntu fournit un profil AppArmor pour le binaire fusermount3, qui n’autorise les montages FUSE que sur une liste positive de motifs de points de montage. Ce profil s’applique aussi à fusermount3 dans le conteneur. Le chemin tel que le conteneur le voit est déterminant :

mount fstype=@{fuse_types} ... -> @{HOME}/**/,
mount fstype=@{fuse_types} ... -> /mnt/{,**/},
mount fstype=@{fuse_types} ... -> /media/**/,
mount fstype=@{fuse_types} ... -> /tmp/**/,

/data ne figure pas dans la liste, /srv non plus. Le fait que le conteneur s’exécute sans confinement ne change rien : le profil est attaché au fichier exécutable, non au conteneur.

La solution exploite le fait que seul fusermount3 est soumis au profil, contrairement à un mount --bind ordinaire : monter FUSE sur un chemin autorisé, puis le publier de là sur le chemin partagé via un bind.

rclone mount remote:pfad /mnt/inner/dokumente --allow-other --vfs-cache-mode full &
# attendre que le montage réponde, puis :
mount --bind /mnt/inner/dokumente /data/dokumente
Explication des options
OptionEffet
remote:pfadRemote Rclone à monter, avec son chemin
/mnt/inner/dokumentePoint de montage sous /mnt, un motif autorisé par le profil AppArmor
--allow-otherAutorise des utilisateurs autres que celui qui a monté le système à accéder au montage FUSE
--vfs-cache-mode fullMet entièrement en cache local les accès en lecture et en écriture
&Lance le montage en arrière-plan afin de libérer le shell pour le bind
mount --bind quelle zielPublie le montage FUSE via un bind sur le chemin partagé ; en tant qu’appel mount(2), il n’est pas soumis au profil fusermount3

Le bind est un appel mount(2) normal et se propage comme tout autre montage vers l’hôte via le chemin shared. Cela a pu être vérifié jusque dans un second conteneur, qui pouvait lire les fichiers avec l’uid 1000. --allow-other est obligatoire dès qu’un autre utilisateur que celui qui a effectué le montage accède aux fichiers ; dans le conteneur Rclone, user_allow_other doit à cette fin figurer dans /etc/fuse.conf (c’est déjà le cas dans l’image officielle).

3. Les consommateurs ont besoin de rslave

Le troisième problème concerne l’autre côté. Si le processus Rclone s’arrête et que le montage est recréé, l’hôte le voit immédiatement. En revanche, un conteneur qui a intégré le chemin par un bind ordinaire ne le voit pas :

ls: cannot access '/usr/src/app/media': Transport endpoint is not connected

Docker utilise par défaut rprivate pour les montages bind : un montage créé sur l’hôte après le démarrage du conteneur n’atteint jamais son espace de noms de montage. Le conteneur reste attaché au montage FUSE désormais déconnecté jusqu’à ce qu’il soit recréé. La solution tient en une ligne :

    volumes:
      - type: bind
        source: /srv/storage/media
        target: /usr/src/app/media
        bind:
          propagation: rslave

Avec rslave, l’hôte transmet les nouveaux événements de montage au conteneur. Lors du test, le consommateur a de nouveau vu tous les fichiers après qu’un montage a été brutalement interrompu puis recréé, sans son propre redémarrage. Le compteur de redémarrages est resté à zéro.

Restauration sans intervention manuelle

Les trois éléments forment un schéma global robuste qui fonctionne sans démon de surveillance :

  1. Le conteneur de montage vérifie ses montages dans une boucle. Si l’un d’eux ne répond plus, il se termine avec un code d’erreur.
  2. restart: unless-stopped permet à Docker de redémarrer le conteneur.
  3. Au démarrage, le conteneur nettoie d’abord les montages orphelins de l’exécution précédente : un bind orphelin sur le chemin cible bloquerait sinon une nouvelle publication, et un utilisateur non privilégié ne peut pas le supprimer depuis l’hôte. Cela fonctionne dans le conteneur, et le umount se propage vers l’extérieur :
while grep -q " /data/dokumente " /proc/self/mountinfo; do
    umount -l /data/dokumente 2>/dev/null || break
done
Explication des options
OptionEffet
grep -qAucune sortie ; seul le code de sortie indique si le chemin figure encore comme montage dans /proc/self/mountinfo
umount -lDémontage différé : retire immédiatement le montage de l’arborescence et ne nettoie les références que lorsqu’il n’est plus utilisé
2>/dev/nullSupprime les messages d’erreur de umount
|| breakTermine la boucle si un umount échoue, au lieu de continuer indéfiniment
  1. Monter et publier ensuite normalement ; les consommateurs avec rslave adoptent automatiquement le nouveau montage.

Lors du test, l’ensemble de la chaîne a duré 160 secondes : le processus Rclone a été arrêté, l’erreur détectée, le conteneur redémarré, le montage orphelin supprimé et le nouveau montage republié. Le conteneur consommateur a continué à fonctionner pendant ce temps et n’a remarqué qu’une brève interruption.

Ceux qui exploitent Rclone directement sur l’hôte via systemd évitent les deux premiers problèmes et n’ont besoin que de rslave sur les conteneurs consommateurs. Le conteneur supplémentaire vaut surtout la peine si l’hôte doit rester exempt d’installations Rclone ou si plusieurs montages doivent être gérés de manière uniforme. Dans ce cas, les trois niveaux doivent être configurés consciemment.

Sources

  1. Docker: Bind mounts: configure bind propagation

    les modes de propagation rprivate, rslave et rshared, ainsi que leur comportement par défaut.

    https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation
  2. Documentation du noyau : Shared Subtrees

    la propagation des montages du noyau Linux, sur laquelle reposent les options bind de Docker.

    https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
  3. Rclone mount

    les modes de cache VFS, —allow-other et les limites du montage FUSE.

    https://rclone.org/commands/rclone_mount/
  4. Documentation AppArmor (Ubuntu)

    comment les profils sont liés aux fichiers exécutables ; le profil fusermount3 se trouve sous /etc/apparmor.d/fusermount3.

    https://documentation.ubuntu.com/server/how-to/security/apparmor/

Commentaires

Les commentaires sont chargés depuis GitHub / Giscus.