qBittorrent
Runs inside the gluetun VPN tunnel (network_mode: service:gluetun), so it publishes no
ports of its own — the Web UI is reachable on gluetun’s 21287.
Config is a bind mount at /volume1/docker-ssd/qbittorrent/appdata → /config. Being a
bind rather than a named volume, it survives docker compose down -v. The stack’s
backup job captures it nightly; the manual snapshot below is for
deliberate pre-upgrade copies.
Namespace orphaning
Section titled “Namespace orphaning”Docker binds a shared network namespace at container start and never re-attaches it.
When gluetun restarts it gets a new namespace, and qbittorrent and rustatio are left
in the old destroyed one — still running, still reporting healthy, reachable by nothing.
The symptom is a connection reset on :21287 and :21478 while both containers log that
they are listening: true, just in a namespace nothing routes to.
Three mitigations are in place:
HEALTH_VPN_DURATION_INITIAL=30son gluetun (../gluetun/gluetun.yaml). The 6s default is shorter than a cold tunnel takes, so gluetun would kill itself during startup. This addresses the trigger.- Healthchecks that can see the fault, plus
deunhealth.restart.on.unhealthy=trueand thedeunhealthwatchdog (../../host/deunhealth.yaml). Each probe hits gluetun’s control server on127.0.0.1:8000, which exists only in gluetun’s live namespace, so an orphaned container fails it. A localhost-only probe cannot — that is why the built-in checks passed through the outage that prompted this. service:gluetunrather thancontainer:gluetun, withdepends_on: condition: service_healthy, so dependants do not start before the tunnel.
deunhealth recovers gluetun restarting with the same container ID. It cannot recover
gluetun being recreated: the dependants’ stored container:<old-id> reference is then
dead and they exit 128 on start. Recovery there is docker compose up -d, which
recreates them too — not a restart.
The fragility is inherent to sharing a namespace; every remedy is detect-and-restart. Removing it entirely would mean running gluetun and its dependants as one unit.
Version pinning
Section titled “Version pinning”harbor.toolsera.lan/lscr_proxy_cache/linuxserver/qbittorrent:5.2.3_v2.0.14-ls471The tag encodes qBittorrent 5.2.3, libtorrent v2.0.14 and LinuxServer build ls471.
The pin is deliberate: qBittorrent rewrites qBittorrent.conf into the new format on first
start after an upgrade, and older builds will not read a migrated config. Upgrades are
effectively one-way, so this file does not track :latest.
Back up appdata before any version bump
Section titled “Back up appdata before any version bump”Archives go to /volume2/docker-apps-backup/qbittorrent/ — pool2, while the source is on
pool1, so they sit on independent storage and outside every compose project directory.
TS=$(date +%Y%m%d-%H%M%S)DEST=/volume2/docker-apps-backup/qbittorrent/qbittorrent-appdata-$TS.tar.gzmkdir -p "$(dirname "$DEST")"tar --exclude=appdata/qBittorrent/ipc-socket \ -czf "$DEST" -C /volume1/docker-ssd/qbittorrent appdatasha256sum "$DEST" | tee "$DEST.sha256"ipc-socket is a Unix socket, which tar cannot archive and qBittorrent recreates. Nothing
else is excluded, so the file counts should match exactly — that equality is the check
worth running:
tar -tzf "$DEST" | grep -v '/$' | wc -lfind /volume1/docker-ssd/qbittorrent/appdata -type f | wc -lgzip -t "$DEST" && echo GZIP_OKThat is a hot backup. Config files are consistent enough for a rollback safety net, but
a running client can leave BT_backup fastresume state mid-write, so torrent progress is
not guaranteed to restore cleanly. For a guaranteed copy, docker stop qbittorrent first
and bring it back with bws run … docker compose up -d qbittorrent.
Restoring
Section titled “Restoring”docker stop qbittorrenttar -xzf /volume2/docker-apps-backup/qbittorrent/qbittorrent-appdata-<timestamp>.tar.gz \ -C /volume1/docker-ssd/qbittorrentbws run --access-token "$BWS_ACCESS_TOKEN" -- docker compose up -d qbittorrentThe tarball’s root entry is appdata/, so it lands back in place; archives taken before
appdata moved to /volume1 restore the same way with a different -C target. Restoring
a config also means reverting the image: tag to the build that wrote it.