Operar montajes de Rclone en Docker de forma fiable
Para que un montaje FUSE de un contenedor funcione también en el host y en otros contenedores, deben combinarse la propagación de montajes, AppArmor y la recuperación tras fallos.
Un montaje de Rclone se ejecuta en un contenedor Docker, pero también debe estar disponible en el host y en otros contenedores. Para ello, los eventos de montaje deben atravesar varios espacios de nombres. Una sola opción de Compose no es suficiente.
En una prueba práctica con Ubuntu 25.10, kernel 6.17 y Docker 29.6 aparecieron tres fallos independientes: Docker degradaba rshared sin avisar, AppArmor bloqueaba fusermount3, y un contenedor consumidor seguía vinculado al montaje antiguo tras reiniciarse. El caso de uso concreto era un almacenamiento en la nube para Paperless-ngx; los mismos mecanismos también se aplican a otras herramientas FUSE como sshfs.
1. La fuente del host debe ser shared por sí misma
Para que un montaje desde el contenedor llegue al host, el bind necesita la propagación rshared:
volumes:
- type: bind
source: /srv/storage/media
target: /data
bind:
propagation: rshared
rshared solo funciona si la fuente del bind en el host es a su vez un punto de montaje con propagación shared. Un directorio normal no cumple este requisito. Aun así, Docker no muestra ningún error, sino que utiliza silenciosamente una propagación más débil. Esto puede comprobarse en /proc/self/mountinfo dentro del contenedor:
1938 2077 8:2 /srv/storage/media /data rw,relatime master:1 - ext4 /dev/sda2 rw
master:1 significa propagación slave: los montajes entran desde el host, pero nunca salen. Lo correcto sería shared:N. La solución consiste en hacer un bind de la fuente sobre sí misma y marcarlo como shared:
mount --bind /srv/storage/media /srv/storage/media
mount --make-shared /srv/storage/media
Para que sobreviva a un reinicio, debe incluirse en una unidad systemd con Before=docker.service. Comprobación: findmnt -no PROPAGATION /srv/storage/media debe devolver shared.
2. AppArmor también comprueba fusermount3 dentro del contenedor
Con la propagación correcta, surgió el siguiente problema. El montaje en la ruta compartida seguía fallando:
NOTICE: mount helper error: fusermount3: mount failed: Permission denied
CRITICAL: Fatal error: failed to mount FUSE fs: fusermount: exit status 1
Los permisos adicionales habituales del contenedor no cambiaron nada: ni CAP_SYS_ADMIN ni /dev/fuse, ni unconfined ni siquiera --privileged. Un montaje tmpfs funcionaba en el mismo destino, y FUSE funcionaba en otras rutas. Solo el registro de auditoría del kernel mostró la causa real:
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 incluye un perfil AppArmor para el binario fusermount3 que permite montajes FUSE únicamente en una lista positiva de patrones de puntos de montaje. Este perfil también se aplica a fusermount3 dentro del contenedor. Lo decisivo es la ruta tal como la ve el contenedor:
mount fstype=@{fuse_types} ... -> @{HOME}/**/,
mount fstype=@{fuse_types} ... -> /mnt/{,**/},
mount fstype=@{fuse_types} ... -> /media/**/,
mount fstype=@{fuse_types} ... -> /tmp/**/,
/data no figura en la lista, /srv tampoco. Que el contenedor se ejecute sin confinamiento no ayuda: el perfil está ligado al archivo ejecutable, no al contenedor.
La salida aprovecha que solo fusermount3 está sujeto al perfil, mientras que un mount --bind normal no lo está: montar FUSE en una ruta permitida y publicarlo desde allí mediante un bind en la ruta compartida.
rclone mount remote:pfad /mnt/inner/dokumente --allow-other --vfs-cache-mode full &
# esperar hasta que el montaje responda; después:
mount --bind /mnt/inner/dokumente /data/dokumente
El bind es una llamada mount(2) normal y, como cualquier otra, se propaga al host a través de la ruta shared. Pudo verificarse incluso en un segundo contenedor, que podía leer los archivos como uid 1000. --allow-other es obligatorio en cuanto un usuario distinto del que realiza el montaje accede a los archivos; para ello, en el contenedor de Rclone debe figurar user_allow_other en /etc/fuse.conf (ya es el caso en la imagen oficial).
3. Los consumidores necesitan rslave
El tercer problema afecta al otro lado. Si el proceso de Rclone falla y se reconstruye el montaje, el host lo ve de inmediato. Sin embargo, un contenedor que ha integrado la ruta mediante un bind normal no lo ve:
ls: cannot access '/usr/src/app/media': Transport endpoint is not connected
Docker utiliza rprivate de forma predeterminada para los bind mounts: un montaje que surge en el host después de iniciar el contenedor nunca llega a su espacio de nombres de montaje. El contenedor queda bloqueado en el montaje FUSE ya desconectado hasta que se recrea. La solución cuesta una línea:
volumes:
- type: bind
source: /srv/storage/media
target: /usr/src/app/media
bind:
propagation: rslave
Con rslave, el host reenvía los nuevos eventos de montaje al contenedor. En la prueba, tras finalizar forzosamente y reconstruir el montaje, el consumidor volvió a ver todos los archivos sin reiniciarse. El contador de reinicios permaneció en cero.
Recuperación sin intervención manual
De los tres elementos resulta un patrón global robusto que no requiere un demonio watchdog:
- El contenedor de montaje comprueba sus montajes en un bucle. Si uno deja de responder, finaliza con un código de error.
restart: unless-stoppedhace que Docker reinicie el contenedor.- Al iniciarse, el contenedor primero elimina los montajes huérfanos de la ejecución anterior: de lo contrario, un bind huérfano en la ruta de destino bloquearía la publicación de nuevo, y un usuario sin privilegios no puede eliminarlo desde el host. En el contenedor sí es posible, y el umount se propaga hacia fuera:
while grep -q " /data/dokumente " /proc/self/mountinfo; do
umount -l /data/dokumente 2>/dev/null || break
done
- Después, montar y publicar normalmente; los consumidores con
rslaveadoptan el montaje nuevo automáticamente.
En la prueba, toda la cadena duró 160 segundos: se terminó el proceso de Rclone, se detectó el fallo, se reinició el contenedor, se eliminó el montaje huérfano y se volvió a publicar el nuevo montaje. El contenedor consumidor siguió ejecutándose mientras tanto y solo notó una breve interrupción.
Quien ejecute Rclone directamente en el host mediante systemd evita los dos primeros problemas y solo necesita rslave en los contenedores consumidores. El contenedor adicional merece la pena sobre todo si el host debe permanecer libre de instalaciones de Rclone o si se quieren gestionar varios montajes de forma uniforme. En ese caso, los tres niveles deben configurarse conscientemente.
Comentarios
Los comentarios se cargan desde GitHub / Giscus.