Deploying Wireguard and qBittorrent container stack

2024-11-15

6 min read

There are numerous existing tutorials about setting up qBittorrent and Wireguard containers, but many neglected enabling IPv6 for the containers. Another commonly neglected point is you can tunnel IPv6 traffic over a v4-only Wireguard connection, so one can torrent over IPv6 even if they don't have a global IPv6 address. As a result, their qBittorrent setups lost a great percentage of peers that are only reachable via IPv6. This guide walks you through creating a Docker Compose stack that includes a Wireguard and a qBittorrent container. With this setup, we'll have a 24/7 torrenting service that has maximal IPv6 capability.

Install Docker

This setup assumes a Debian 12 (Bookworm) server environment, but any Linux distribution with Docker will work. Install Docker using your package manager, or run the official installation script:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

Enable Docker IPv6 Support

By default, Docker's IPv6 support is experimental. (Yes, you hear it right: Docker's IPv6 support is STILL in experimental stage in 2024.) To enable it, edit (or create) /etc/docker/daemon.json and add the following configuration:

{
  "experimental": true,
  "ip6tables": true,
  "fixed-cidr-v6": "2001:db8:1::/64"  # Optional, replace with your desired GUA range for containers
}

These flags enable Docker's IPv6 functionality. The optional fixed-cidr-ipv6 flag sets a default address range for all the containers created directly with the docker command. But since we create our application containers using Docker Compose, the default range won't apply. We'll need to manually set up a range for each container network that needs IPv6 connectivity.

Per official Docker guide on IPv6 networking, we don't have to set the IPv6 flag in order to use IPv6 networking in containers. My testing, however, tells otherwise.

Restart Docker daemon to apply the changes. You won't see anything special, but we'll know it's working when we run our qBittorrent container.

sudo systemctl restart docker

Create Wireguard and qBittorrent containers

Create a directory for our application stack (e.g., qbittorrent), and navigate into it. Create a file named docker-compose.yaml with the following content:

services:
  wireguard:
    image: linuxserver/wireguard
    container_name: wireguard
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Los_Angeles  # Change to your time zone
    volumes:
      - ./wg-config:/config
      - /lib/modules:/lib/modules
    ports:
      - "127.0.0.1:8080:${QBT_WEBUI_PORT}"
      - "[::1]:8080:${QBT_WEBUI_PORT}"
      - "12345:12345/udp"  # Replace with your VPN's port-forwarding configuration
      - "51820:51820/udp"  # Default Wireguard port
    networks:
      - qbt-wg-network
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1  # Required for Wireguard container. See https://github.com/linuxserver/docker-wireguard
      - net.ipv6.conf.all.disable_ipv6=0  # Required for IPv6 capability
    restart: unless-stopped

  qbittorrent-nox:
    container_name: qbittorrent-nox
    environment:
      - PUID=1000
      - PGID=1000
      - QBT_EULA=${QBT_EULA}
      - QBT_VERSION=${QBT_VERSION}
      - QBT_WEBUI_PORT=${QBT_WEBUI_PORT}
      - TZ=America/Los_Angeles  # Change to your time zone
    image: qbittorrentofficial/qbittorrent-nox:${QBT_VERSION}
    network_mode: service:wireguard
    read_only: true
    restart: unless-stopped
    stop_grace_period: 30m
    tmpfs:
      - /tmp
    volumes:
      - ${QBT_CONFIG_PATH}:/config
      - ${QBT_DOWNLOADS_PATH}:/downloads

networks:
  qbt-wg-network:
    enable_ipv6: true
    ipam:
      config:
        # Set this to a subnet that won't be used by any GUA address. See the explanation below.
        - subnet: d000::/112  # Change to your favorite CIDR

In the same folder, create an .env file with the environmental variables:

QBT_EULA=accept
QBT_VERSION=latest
QBT_WEBUI_PORT=8080

QBT_CONFIG_PATH=./qb-config
QBT_DOWNLOADS_PATH=/path/to/your/media/drive

Some remarks on the Docker Compose config:

  1. We need a sysctl flag net.ipv4.conf.all.src_valid_mark=1 to make Wireguard container work properly. Getting it working under IPv6 requires another flag net.ipv6.conf.all.disable_ipv6=0.
  2. As a result, we also need to add the NET_ADMIN capability to set the flags and the SYS_MODULE capability to load Wireguard kernel module.
  3. To utilize the port-forwarding feature of our VPN, we need to expose that UDP port (12345 in this example) in the config.
  4. We also need to set an IPv6 subnet for the container network if we want IPv6 connectivity. The subnet should be a GUA address that won't be used by any existing device, for example d000::/112.

Add Wireguard Config

Cool, let's run the stack once to initialize the configuration directory:

docker compose up -d
docker compose down

We should see a wg-config folder now. Grab your Wireguard config from your VPN provider, and move it to wg-config/wg_confs/wg0.conf. Here's the config generator setup I used at AirVPN (NOT affiliated).

airvpn config generator
AirVPN Config Generator, Advanced Settings

Add some routing rules to the config, or we wouldn't be able to access the qBittorrent UI. Here's what the config file should look like after editing:

[Interface]
Address = <redacted>, <redacted_ipv6>
PrivateKey = <redacted>
ListenPort = 51820
MTU = 1320
DNS = <redacted>, <redacted_ipv6>

# The following rule excludes 172.16.0.0/12 and 192.168.0.0/16 from Wireguard, so it can interact with the container host.
# Excluding the ranges using `AllowedIPs` would also work but ugly
# Check https://www.procustodibus.com/blog/2021/03/wireguard-allowedips-calculator/ to see how complicated the range would be
PostUp = DROUTE=$(ip route | grep default | awk '{print $3}'); HOMENET=192.168.0.0/16; HOMENET2=172.16.0.0/12; ip route add $HOMENET via $DROUTE; ip route add $HOMENET2 via $DROUTE; iptables -I OUTPUT -d $HOMENET -j ACCEPT;iptables -A OUTPUT -d $HOMENET2 -j ACCEPT;  iptables -A OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = HOMENET=192.168.0.0/16; HOMENET2=172.16.0.0/12; ip route delete $HOMENET; ip route delete $HOMENET2; iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT; iptables -D OUTPUT -d $HOMENET -j ACCEPT; iptables -D OUTPUT -d $HOMENET2 -j ACCEPT

[Peer]
PublicKey = <redacted>
PresharedKey = <redacted>
Endpoint = <redacted>
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 15

If your home network lacks IPv6 access, remove the IPv6 endpoints in the Address and DNS fields and ensure the peer Endpoint address is reachable via IPv4.

Restart the service.

docker compose up -d

You should be able to access the qBittorrent web UI over port 8080.

Configure qBittorrent client

We are almost done. Let's configure the qBittorrent client now.

  1. Open the qBittorrent web UI. Navigate to Tools->Options->Connections, and set the Listening Ports to match the VPN's forwarded port.
  2. Follow the qBittorrent optimization guide from your VPN provider website.
  3. (Optionally) set qBittorrent to use the VPN interface exclusively, so that we can safely download our Linux ISOs. Go to Tools->Options->Advanced, and select wg0 from the Network interface dropdown. Remember to scroll all the way down and click Save!
  4. On the other hand, if you don't want to use your VPN, you can select eth0 or whatever interface from the same dropdown and save.

Have fun!