Hello all, Anyone using a side car container to keep Gluetun-qBittorrent Proton VPN Port Forward in sync? My research suggested the following compose: services: qbit-gluetun-sync: image: ghcr.io/hononeko/qbit-gluetun-sync:latest containername: qbit-gluetun-sync restart: unless-stopped networkmode: "service:sb-gluetun" environment: GLUETUNPORTFILE: "/tmp/gluetun/forwardedport" QBITTORRENTURL: "http://localhost:8080" QBITTORRENTUSERNAME: "admin" QBITTORRENTPASSWORD: "yourpassword" Thanks!
16 replies
tomspark wrote:
Good question! SparkBox includes both Gluetun and qBittorrent as part of the Media Center module, so port forwarding is definitely in scope. The qbit-gluetun-sync sidecar you found is a nice approach—it automates keeping qBittorrent's listen port in sync with Proton's forwarded port, which is helpful since Proton rotates the port periodically. Before deploying the sidecar: make sure your base Gluetun + Proton setup is working first. The SparkBox docs (docs/MEDIA-GUIDE.md in your /opt/sparkbox) walk through Proton VPN setup step-by-step, including the NAT-PMP setting and P2P server requirement—those are the essentials. Once your port shows up in docker exec sb-gluetun wget -qO- ifconfig.me, the sidecar will have something to sync. Give the sidecar a try and let us know how it works out—community wins like this are worth documenting!
tomspark wrote:
Not built in yet — you found a real gap: qBittorrent's listen port doesn't follow gluetun's forwarded port on its own, so a sync sidecar is the right idea. I've logged making this automatic as a feature request. Two SparkBox-specific tweaks. Don't run it as a separate stack — drop it in /opt/sparkbox/state/overrides/media.yml, which gets merged into the media compose on every sudo sparkbox up and survives updates (see docs/PER-APP-OVERRIDES.md in your install). In there the service name is gluetun, and gluetun writes the forwarded-port file to its own private /tmp, so give both sides a shared folder: services: gluetun: volumes: - /opt/sparkbox/state/gluetun-pf:/tmp/gluetun qbit-gluetun-sync: image: <your sync image networkmode: "service:gluetun" restart: unless-stopped volumes: - /opt/sparkbox/state/gluetun-pf:/tmp/gluetun:ro qBittorrent's WebUI creds live in /opt/sparkbox/state/qbittorrent-admin-password.txt. I haven't vetted that particular sync image myself, so usual third-party caution applies.
kiltmann wrote:
Thanks! I'll give give it a try and let you know what happens.
kiltmann wrote:
The /opt/sparkbox/state/overrides/media.yml file does not exist.
tomspark wrote:
Ah — that file doesn't exist yet because it's yours to create, not something SparkBox ships. That path is SparkBox's per-app override slot: make the file at state/overrides/media.yml inside your SparkBox folder, drop your qbit-gluetun-sync container into it as a normal compose service, and the next run of sudo sparkbox up will layer it on automatically and keep it across updates. So: create that file, paste the sidecar service into it, then run sudo sparkbox up. If it doesn't come up cleanly, paste the output here and I'll take a look. 🙂
kiltmann wrote:
Here is the result: sudo sparkbox up / | | | | ) \ \| ' \ / | '| |/ / \ / \ \/ / ) | |) | (| | | | <| |) | () < |/| ./ \,|| ||\\/ \//\\ || SparkBox v1.6.369 - Self-Hosted Privacy Stack Created by Tom Spark | youtube.com/@TomSparkReviews Network profile: private [INFO] Starting SparkBox... [INFO] Active modules: adguard audiobookshelf core dashboard files homarr immich media monitoring navidrome paperless stirling-pdf tdarr [INFO] Prepared 3 module config dir(s) for PUID=1000 PGID=1000 [INFO] .env changed since last up — recreating the affected containers to pick up new values [+] up 1/1 ✔ Container sb-adguard Running 0.0s [+] up 1/1 ✔ Container sb-audiobookshelf Running 0.0s [+] up 3/3 ✔ Container sb-portainer Running 0.0s ✔ Container sb-homepage Running 0.0s ✔ Container sb-npm Running 0.0s [+] up 1/1 ✔ Container sb-filebrowser Running 0.0s [+] up 1/1 ✔ Container sb-homarr Running 0.0s [+] up 4/4 ✔ Container sb-immich-db Healthy 0.6s ✔ Container sb-immich-redis Healthy 0.6s ✔ Container sb-immich-server Running 0.0s ✔ Container sb-immich-ml Running 0.0s [ERROR] Module 'media' has a broken compose file — NOT bringing it up (a YAML error here would crash the stack with a misleading per-service message): [ERROR] /opt/sparkbox/modules/media/docker-compose.yml service "sb-gluetun" has neither an image nor a build context specified: invalid compose project [ERROR] Recover with 'sudo sparkbox update' (re-renders the compose; your settings in .env are untouched), or fix the YAML above, then retry. [+] up 1/1 ✔ Container sb-uptime-kuma Running 0.0s [+] up 1/1 ✔ Container sb-navidrome Running 0.0s [+] up 3/3 ✔ Container sb-paperless Running 0.0s ✔ Container sb-paperless-db Healthy 0.6s ✔ Container sb-paperless-redis Healthy 0.6s [+] up 1/1 ✔ Container sb-stirling-pdf Running 0.0s [+] up 1/1 ✔ Container sb-tdarr Running
tomspark wrote:
Ah, I see the issue — the override example I gave redefines gluetun with only volumes, so when docker-compose merges it, the image gets stripped out. Here's the corrected version: yaml services: gluetun: image: qmcgaw/gluetun:v3.41.1 volumes: - /opt/sparkbox/state/gluetun-pf:/tmp/gluetun qbit-gluetun-sync: image: <your-sync-image networkmode: "service:gluetun" restart: unless-stopped volumes: - /opt/sparkbox/state/gluetun-pf:/tmp/gluetun:ro The key: include image: qmcgaw/gluetun:v3.41.1 when you override gluetun's volumes. Now sudo sparkbox up media should work. Let me know!
tomspark wrote:
Good news, nothing actually broke - SparkBox caught a bad config before it could take down your other apps, which is why everything else in that list still shows Running. My guess: in your state/overrides/media.yml, the block might be named sb-gluetun instead of gluetun - that sb- prefix is just gluetun's container nickname, but the file needs its real internal name (gluetun, no prefix) to line up. Open that file, check the entry under services: reads gluetun, save, then sudo sparkbox up again. Paste the file's contents here if it still won't come up.
kiltmann wrote:
This config worked when I ran sudo sparkbox up media: services: gluetun: image: qmcgaw/gluetun:v3.41.1 volumes: - /opt/sparkbox/state/gluetun-pf:/tmp/gluetun qbit-gluetun-sync: image: ghcr.io/hononeko/qbit-gluetun-sync:latest containername: qbit-gluetun-sync restart: unless-stopped networkmode: "service:gluetun" dependson: - gluetun volumes: - /opt/sparkbox/state/gluetun-pf:/tmp/gluetun:ro environment: GLUETUNPORTFILE: "/tmp/gluetun/forwardedport" QBITTORRENTURL: "http://localhost:8080" QBITTORRENTUSERNAME: "${QBITSYNCUSERNAME}" QBITTORRENTPASSWORD: "${QBITSYNCPASSWORD}"
kiltmann wrote:
It does not seem to have fired after restarting the media stack. I put the new port into qbittorent manuall.
tomspark wrote:
Good catch — manual sync confirms the setup is solid. The qbit-gluetun-sync is a community tool I haven't fully vetted, so auto-fire depends on the image's own design. Check the logs: docker logs qbit-gluetun-sync — it'll show if it's running. Also verify env vars (GLUETUNPORTFILE) reach the container correctly. If the image lacks a scheduler, you might need a cronjob wrapper instead.
tomspark wrote:
Good that you've got a manual workaround going for now! To see why the sync container itself isn't firing, run docker logs qbit-gluetun-sync and paste what it shows - that'll tell us if it's reading the port file, erroring out, or just not starting. That'll point at the real fix instead of us guessing.
kiltmann wrote:
sudo docker logs qbit-gluetun-sync [sudo] password for kiltmann: time=2026-07-17T01:25:12.238Z level=INFO msg="Starting watcher" file=/tmp/gluetun/forwardedport time=2026-07-17T01:25:12.238Z level=INFO msg="Starting sidecar server" listenPort=9090 qbitAddr=http://localhost:8080 time=2026-07-17T01:25:21.047Z level=INFO msg="Syncing new port to qBitTorrent" port=55963 time=2026-07-17T01:25:21.048Z level=WARN msg="Failed to set port" attempt=1 maxRetries=5 err="setPreferences failed with status: 403, body: Forbidden" time=2026-07-17T01:25:21.048Z level=INFO msg=Retrying... backoff=1s time=2026-07-17T01:25:22.049Z level=WARN msg="Failed to set port" attempt=2 maxRetries=5 err="setPreferences failed with status: 403, body: Forbidden" time=2026-07-17T01:25:22.049Z level=INFO msg=Retrying... backoff=2s time=2026-07-17T01:25:24.050Z level=WARN msg="Failed to set port" attempt=3 maxRetries=5 err="setPreferences failed with status: 403, body: Forbidden" time=2026-07-17T01:25:24.050Z level=INFO msg=Retrying... backoff=4s time=2026-07-17T01:25:28.054Z level=WARN msg="Failed to set port" attempt=4 maxRetries=5 err="setPreferences failed with status: 403, body: Forbidden" time=2026-07-17T01:25:28.054Z level=INFO msg=Retrying... backoff=8s time=2026-07-17T01:25:36.061Z level=WARN msg="Failed to set port" attempt=5 maxRetries=5 err="setPreferences failed with status: 403, body: Forbidden" time=2026-07-17T01:25:36.061Z level=ERROR msg="Exhausted all retries. Failed to sync port to qBitTorrent" port=55963 err="setPreferences failed with status: 403, body: Forbidden"
tomspark wrote:
Good debugging logs — the 403 confirms auth is failing. The env var issue: ${QBITSYNCUSERNAME} and ${QBITSYNCPASSWORD} aren't substituted in overrides the way you'd expect. Quick fix: hardcode the credentials directly in media.yml: qbit-gluetun-sync: environment: QBITTORRENTUSERNAME: admin QBITTORRENTPASSWORD: (paste the password from: cat /opt/sparkbox/state/qbittorrent-admin-password.txt | grep password) Then sudo sparkbox up media. If the password contains special chars, wrap it in single quotes. Let us know if 403 clears after that!
kiltmann wrote:
The hardcoding worked. You have to check the Bypass authentication for clients on localhost selection in qBittorrent WebUI.
Chris wrote:
Nice, glad the hardcode did it — and thanks for posting the WebUI bypass-auth tip too, that'll save the next person hitting this same 403. Enjoy the auto port sync!