What it is
A host-level systemd service on the media LXC that runs rclone mount against a WebDAV endpoint. The result is a host-level FUSE mount that Plex, Seanime, and Riven bind into their containers and read like any other media library.
zurg is the bridge between the upstream media API and a WebDAV server. rclone is the bridge between WebDAV and a kernel FUSE mount. Together they turn "remote files" into "directories on disk" without downloading the catalogue to the host.
Why I run it (this way, specifically)
The original design had rclone as a sibling container in the media stack — clean, self-contained, lived inside one compose file. It didn't work, for a reason I had to learn the hard way:
FUSE file-descriptor lifetime is tied to the rclone process. When the rclone container restarts, the FUSE FD it created in the kernel goes away. Consumer containers' bind mounts are kernel references to that specific FD. New rclone creates a new FUSE mount with a new FD — but the consumers are still pointing at the dead one. Every docker compose up -d that touched the rclone service broke Plex, Seanime, and Riven simultaneously.
Bind-mount propagation (rshared and friends) does not fix this. The consumer mounts were peers of the previous shared peer group, not the new one. The propagation worked perfectly; the consumers were just peers of the wrong thing.
The fix is to decouple the FUSE mount from Docker's lifecycle entirely. rclone now runs as a host systemd service. Consumer containers bind-mount the host path managed by that service, which is alive as long as the systemd service is. docker compose up -d no longer touches rclone, full stop.
How it works
zurg container (WebDAV server)
│
│ host-level rclone (systemd) FUSE-mounts the WebDAV
▼
host FUSE mount
│
│ bind-mounted into Plex / Seanime / Riven
▼
consumer containers see the media catalogue
The systemd unit has a few non-obvious requirements:
MountFlags=sharedin the[Service]section. Without it, the FUSE mount only exists inside the service's private mount namespace — invisible to Docker and to normal mount inspection. This setting cost 30+ minutes of diagnosis the first time.- An
ExecStartPrethat polls until zurg's WebDAV port is reachable. On a fresh LXC boot, rclone can race ahead of zurg's container starting and enter a tight restart loop trying to read the empty WebDAV. Type=simple(notType=notify) becauserclone mountdoesn't send sd_notify ready signals.- An explicit unmount on stop, otherwise the mount ends up in a "Transport endpoint is not connected" zombie state and the next start fails.
Setup notes
- Host: the media LXC. FUSE passthrough is configured at the Proxmox host level (
/dev/fusecgroup permission + bind mount) — unprivileged LXCs don't have FUSE by default. - rclone version: pinned, downloaded from rclone.org directly. Not from a distro package.
- Boot-time auto-recovery: a companion
rclone-zurg-consumer-refresh.serviceoneshot fires on every LXC boot and runs a helper script that restarts rclone and the consumer containers in the right order. This makes the nightly stop-mode backup of the LXC self-healing — the reboot used to leave consumers in a stale-FD state until I ran the helper manually every morning. - Reverse proxy: not applicable.
- Backups: the unit file and the helper script live in the LXC's rootfs and are captured by PBS.
Runbook
- Healthy looks like: the systemd service shows
active (running), mount inspection showsfuse.rclone, and the host mount lists the expected top-level directories. - The host mount is empty: rclone-zurg.service either isn't running, or
MountFlags=sharedis missing from the unit.journalctl -u rclone-zurg.servicewill say which. - Service fails with
fusermount3: executable file not found: thefuse3package isn't installed. The legacyfusepackage isn't enough for current rclone. Transport endpoint is not connectedinside the consumer containers after a manual rclone restart: classic stale FUSE FD. The helper script restarts rclone, waits for the mount, then restarts the consumers in the right order.- Service starts but the mount looks empty to Docker only:
MountFlags=sharedmissing — the mount is in a private namespace. docker compose up -dno longer breaks consumers — that was the original pain point and is solved by the systemd model. Manualsystemctl restart rclone-zurg.servicestill requires consumer restarts (the helper script does it cleanly), but ordinary stack churn doesn't touch rclone at all.- Where logs live:
journalctl -u rclone-zurg.service.