Skip to content

Instantly share code, notes, and snippets.

@quietsy
Last active Aug 9, 2021
Embed
What would you like to do?
Routing Containers Through a VPN

Introduction

This setup allows you to route containers through a VPN and protect yourself from your ISP.

This guide is the basic step-by-step version of great blog posts by Spad and Tokugero which also contain more advanced information.

qBittorrent and Mullvad are used in this guide as an example, but you can route any container the same way, and use any VPN service that supports Wireguard.

Initial qBittorrent Configuration

Configure your qBittorrent container according to the qBittorrent documentation.

  qbittorrent:
    image: ghcr.io/linuxserver/qbittorrent
    container_name: qbittorrent
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
      - WEBUI_PORT=8080
    volumes:
      - /path/to/appdata/config:/config
      - /path/to/downloads:/downloads
    ports:
      - 6881:6881
      - 6881:6881/udp
      - 8080:8080
    restart: unless-stopped

Once done start the container and validate that qBittorrent is working.

Initial VPN Wireguard Client Configuration

Configure your VPN Wireguard Client according to the Wireguard documentation.

  vpn:
    image: ghcr.io/linuxserver/wireguard
    container_name: vpn
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
    volumes:
      - /path/to/appdata/config:/config
      - /lib/modules:/lib/modules
    restart: unless-stopped

Once done start the container and validate that docker logs vpn contains no errors (Ignore the missing wg0.conf message).

Connecting the Wireguard Client to the VPN

Copy the Wireguard configuration that you get from your VPN provider into a file called wg0.conf and place it in your VPN Wireguard Client's config folder, and make the following changes:

  • Remove IPv6 addresses (and ::/0) if you haven't enabled IPv6 in your docker network
  • Add the PostUp and PreDown lines listed below
[Interface]
PrivateKey = <private-key>
Address = <some-address>/32
DNS = <some-address>
PostUp = DROUTE=$(ip route | grep default | awk '{print $3}'); HOMENET=192.168.0.0/16; HOMENET2=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route add $HOMENET3 via $DROUTE;ip route add $HOMENET2 via $DROUTE; ip route add $HOMENET via $DROUTE;iptables -I OUTPUT -d $HOMENET -j ACCEPT;iptables -A OUTPUT -d $HOMENET2 -j ACCEPT; iptables -A OUTPUT -d $HOMENET3 -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=10.0.0.0/8; HOMENET3=172.16.0.0/12; ip route del $HOMENET3 via $DROUTE;ip route del $HOMENET2 via $DROUTE; ip route del $HOMENET via $DROUTE; 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; iptables -D OUTPUT -d $HOMENET3 -j ACCEPT

[Peer]
PublicKey = <public-key>
AllowedIPs = 0.0.0.0/0
Endpoint = <some-address>:<some-port>

The PostUp command adds a killswitch using iptables rules to prevent connections on other interfaces. Connections from LAN networks are still allowed to be able to connect to the services in the containers. The PreDown command cleans up these rules when the VPN goes down.

Save the changes and restart the container with docker restart vpn, validate that docker logs vpn contains no errors.

Perform the following validations to check that the VPN works:

  • Check that you have connectivity by running docker exec vpn ping 1.1.1.1
  • Check that your DNS matches the one in wg0.conf by running docker exec vpn cat /etc/resolv.conf
  • Check that the VPN is working by running docker exec vpn curl -s https://am.i.mullvad.net/ip, you should get an IP that is different from your internet's IP.

Routing qBittorrent Through the VPN

Replace the following lines on the qBittorrent container:

    ports:
      - 6881:6881
      - 6881:6881/udp
      - 8080:8080

With:

    network_mode: "service:vpn"
    depends_on:
      - vpn

Add the port under the VPN Wireguard Client container:

    ports:
      - 8080:8080 # qBittorrent

Recreate the VPN Wireguard Client container to apply the changes, then recreate the qBittorrent container which depends on the VPN.

Perform the following validations to check that the VPN works:

  • Check that your DNS matches the one in wg0.conf by running docker exec qbittorrent cat /etc/resolv.conf
  • Check that the VPN is working by running docker exec qbittorrent curl -s https://am.i.mullvad.net/ip and make sure you don't get your internet's IP.
  • Check that qBittorrent's Web Administration interface is working by browsing http://<server-ip>:8080.

Configuring Other Containers

Now that qBittorrent is routed through the VPN, other containers need to be configured with the change. Instead of qbittorrent they will need to use vpn to reach the qBittorrent container.

SWAG

Edit your qbittorrent.subdomain.conf or qbittorrent.subfolder.conf in SWAG's config folder under config/nginx/proxy-confs/.

Replace all occurrences of set $upstream_app qbittorrent; with set $upstream_app vpn;.

Restart the SWAG to apply the changes with docker restart swag.

Sonarr / Radarr

Under Settings > Download Clients > Click qBittorrent's Download Client > Set Host to vpn > click Test & Save.

Notes

Port forwarding

VPN providers like Mullvad support port forwarding, if your application needs it.

For example in Mullvad > My Account > Manage ports and Wireguard Keys > Follow the instructions to get a port.

Copy the port number you got to qBittorrent > Settings > Connection > Port used for incoming connections.

Restarting order

If you're experiencing problems and you want to restart everything, the correct order is:

  • VPN - docker restart vpn
  • Containers using the VPN - docker restart <container>

Can't connect to the Web-UI of routed containers

Make sure that you have added PostUp and PreDown to wg0.conf as detailed in Connecting the Wireguard Client to the VPN.

@pagdot

This comment has been minimized.

Copy link

@pagdot pagdot commented Feb 13, 2021

I may be wrong, but AFAIK depends_on may not be required because network_mode adds already a dependency

@pagdot

This comment has been minimized.

Copy link

@pagdot pagdot commented Feb 13, 2021

I would also add an explanation of the PostUp and PostDown commands. e.g.:

The post commands add a killswitch using iptable rules to prevent connections on other interfaces. Connections from LAN networks are still allowed to be able to connect to the services in the containers.

Additionally for Routing qBittorrent Through the VPN:

If the container gets accessed only from a docker network (e.g. a reverse proxy, sonarr, radarr, ...), connected to the vpn/wireguard container, expose can be used instead of ports to prevent access from outside. e.g.:

vpn:
    image: ghcr.io/linuxserver/wireguard
    container_name: vpn
    ...
    networks:
      - reverse-proxy
    expose:
      - "8080"  #qBittorrent
@quietsy

This comment has been minimized.

Copy link
Owner Author

@quietsy quietsy commented Feb 14, 2021

Thanks for the comments!
I'm also unsure about depends_on, I guess it doesn't hurt to keep it there.
I added the description to PostUp/Predown.
Regarding Expose, I wanted to stay in-line with the qBittorrent container documentation which recommends opening the port on the host, I guess advanced users know it's not needed when using a reverse proxy.

@stx8

This comment has been minimized.

Copy link

@stx8 stx8 commented Feb 15, 2021

Hi all, I'm having the same issue and glad to have run into this. However, the above didn't help me.
I'm running a bunbutux container, and have containers routed through working perfectly.

I'm just setting up SWAG today, and any containers not routed through are fine, but the ones that are do not work. I tried to edit the .conf as per your instructions, and replaced the container name with the VPN name, and regardless, I keep getting host not found in the logs! Any thoughts?

@quietsy

This comment has been minimized.

Copy link
Owner Author

@quietsy quietsy commented Feb 15, 2021

Hey stx8, tag me on Discord and I'll try to help.

@pagdot

This comment has been minimized.

Copy link

@pagdot pagdot commented Feb 15, 2021

I'd like to add an Troubleshooting section:

Troubleshooting

Can't connect to the Web-UI of connected containers

This can be an issue if you don't use the killswitch implementation from Connecting the Wireguard Client to the VPN. These are implemented in PostUp and PreDown.

The ip table rules from Mullvad (and possible other providers) reject access from LAN networks.
The iptable rules implemented with the commands in PostUp and PreDown from this guide should work with accessing the container from within a LAN network.

Regarding:

Regarding Expose, I wanted to stay in-line with the qBittorrent container documentation which recommends opening the port on the host, I guess advanced users know it's not needed when using a reverse proxy.

Maybe it could be added in the notes section. Without ports or expose the vpn container does not know that qBittorrent (or any other service) listens on those ports because they aren't declared in the Dockerfile. I had to troubleshoot a bit myself when I've dealt with it the first time.

Edit: I've tried to write the section regarding expose instead of ports myself, but had slightly difficulties explaining why you need to specify expose, instead just expecting everything to work without both expose and ports:

Notes

...

Exposing ports of services only in docker networks

By exposing ports of services using ports, they are not only available to other containers on a shared docker network, but also from the host or even another machine if no additional firewall rules prevent access.
It is required to specify all required ports using expose because docker needs to know which ports are to be accessed. This is not required with "normal" containers because the list of exposed ports is already set in the Dockerfile.
If this is unwanted, another option is to use expose instead of ports to expose the ports only on connected docker networks:

vpn:
   image: ghcr.io/linuxserver/wireguard
   container_name: vpn
   ...
   networks:
     - reverse-proxy
   expose:
     - "8080"  #qBittorrent
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment