Summary
The qBittorrent Web UI is unreachable because its network namespace is entirely isolated within the Gluetun VPN container. While Gluetun exposes the torrent port to the host via port mapping (6881), its internal port 8080 (the Web UI) is not exposed to the host in a standard way. Furthermore, attempting to reach qBittorrent via the host IP on port 8080 fails because traffic cannot traverse the docker network bridge into the service:gluetun namespace automatically.
Root Cause
-
Missing Port Mapping on Gluetun:
In thegluetunservice, only the VPN input port (${FIREWALL_VPN_INPUT_PORTS}) is published.ports: - ${FIREWALL_VPN_INPUT_PORTS}:${FIREWALL_VPN_INPUT_PORTS}Port
8080(the qBittorrent Web UI) is not listed here. Without this mapping, the host machine cannot reach port8080inside thegluetuncontainer. -
Incorrect WebUI Configuration in qBittorrent:
The qBittorrent service configures its internal environment to listen on port8080, but the container is sharing its network stack with Gluetun (network_mode: service:gluetun). This meansqBittorrentlistens on the Gluetun container’s IP (172.39.0.2), not the Host IP. Since Gluetun doesn’t forward port8080to the host, the UI is inaccessible from outside the Gluetun container. -
Host-to-Container Routing Failure:
Even though theservarrnetworkexists, theqBittorrentservice does not explicitly attach to it because it usesnetwork_mode: service:gluetun. Therefore, it inherits the Gluetun network interface, which is not bridged to theservarrnetworkon the host.
Why This Happens in Real Systems
- Split-Tunneling Configuration: Developers often assume that port mapping on the VPN container (Gluetun) automatically forwards all ports or that the dependent container (qBittorrent) will inherit the ability to communicate with the host network seamlessly.
- Incorrect Stack Architecture: When sharing a network namespace via
network_mode: service:X, the dependent container loses its ability to publish ports independently. It relies entirely on the parent container (Gluetun) to publish those ports to the host. - Variable Overrides: The environment variable
WEBUI_PORT=8080dictates the internal listening port, but the Docker Compose file fails to translate this internal port to an external host port.
Real-World Impact
- Operational Inaccessibility: The admin cannot access the qBittorrent Web UI to manage torrents, configure settings, or verify connectivity.
- Service Degradation: The *arr stack integration is hampered because manual torrent management is blocked, potentially causing automation failures.
- Debugging Complexity: Logs indicate successful startup, masking the fact that the network topology is misconfigured. This leads to prolonged troubleshooting cycles.
Example or Code
To resolve this, we must expose 8080 on the Gluetun container so the host can reach it, and ensure qBittorrent is configured to use that port.
1. Corrected Gluetun Service (Add port 8080):
services:
gluetun:
image: qmcgaw/gluetun
container_name: gluetun
cap_add:
- NET_ADMIN
ports:
# Add 8080 here
- 8080:8080
# Existing ports
- ${FIREWALL_VPN_INPUT_PORTS}:${FIREWALL_VPN_INPUT_PORTS}
- 6881:6881
# ... other ports
2. Corrected qBittorrent Service (Network Mode Adjustment):
Note: To make the Web UI reachable, qBittorrent must be accessible via the Gluetun IP, but the port must be mapped on Gluetun. Alternatively, and more robustly, attach qBittorrent to the servarrnetwork so it is independently reachable by other services (like Sonarr/Radarr) and map the port directly on qBittorrent. However, strictly fixing the “Unreachable” issue while maintaining VPN traffic for qBittorrent requires the Gluetun port mapping.
If you strictly want to keep qBittorrent behind Gluetun (for VPN traffic only) and access the UI via the Host:
- Add port mapping to Gluetun (as shown above).
- Ensure qBittorrent’s
WEBUI_PORTmatches the internal port Gluetun listens on (8080).
3. Alternative: Hybrid Network (Recommended for Accessibility):
If you want Sonarr/Radarr (on 172.39.0.x) to talk to qBittorrent, network_mode: service:gluetun prevents this. You should allow qBittorrent to attach to the servarrnetwork as well.
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
restart: unless-stopped
# Remove network_mode: service:gluetun if you need external access
# network_mode: "service:gluetun"
# Add network_mode to use Gluetun for VPN but keep bridge for UI
network_mode: "container:gluetun"
# OR configure routes manually.
# To fix strictly without changing routing logic:
# Keep 'network_mode: service:gluetun'
# Ensure Gluetun ports are mapped correctly (Step 1)
How Senior Engineers Fix It
Senior engineers resolve this by enforcing Explicit Port Forwarding and Network Topology Clarity:
- Map External Ports to the VPN Container: Since
qBittorrentshares the network namespace withGluetun,Gluetunacts as the gateway. We must map the Web UI port (8080) on the host to port 8080 inside the Gluetun container. - Verify Internal Configuration: We ensure
WEBUI_PORT=8080is set correctly in theqBittorrentenvironment variables so the application listens on the port Gluetun is forwarding. - Split Network vs. Service Mode: For complex stacks, we often abandon
network_mode: service:Xfor stateful applications likeqBittorrent. Instead, we connectqBittorrentto theservarrnetwork(like Sonarr/Radarr) and useiptablesinside the container or a shared VPN network (likemacvlan) to route traffic through Gluetun. - Validation: We use
curlfrom the host to verify reachability:curl -v http://localhost:8080. If this fails, the port mapping is still incorrect.
Why Juniors Miss It
- Misunderstanding
network_mode: Juniors often believe thatnetwork_mode: service:gluetunsimply routes traffic through the VPN for internet access while keeping the container on the host’s bridge network. In reality, it completely isolates the container’s network stack, making it invisible to the host on all ports except those explicitly mapped by the parent container (Gluetun). - Confusing Internal vs. External Ports: They set
WEBUI_PORT=8080(internal) but forget that Docker requires aportsmapping (external:internal) to access it from the browser. - Log Blindness: Since the logs say “Listening on port 8080,” juniors assume the port is open to the network. They fail to check if the port is actually bound to the host interface (
netstat -tulnordocker ps). - Overlooking the VPN Container as a Firewall: They treat Gluetun as a transparent bridge. However, Gluetun (based on Alpine Linux) often has strict firewall rules (nftables/iptables) that drop incoming connections to non-forwarded ports. The VPN container acts as a firewall/router; if a port isn’t in the config, it’s blocked.