Tunnel traffic of docker containers

Solution: share container network

You want a container to be connected to a Wireguard VPN? You want its traffic to be completely tunneled through the VPN without installing and configuring the VPN client yourself?

I tried in different ways to achieve that, I tried installing the wireguard client into a newly created Dockerfile, using as a base image the wanted base image with the programs you want to run. I tried to play around with iptables rules. None of these solutions were effective, easily reusable and right.

There is an easy solution: container’s network sharing.

Using Docker Compose, you can define the containers you need and then add a wireguard client container in the same compose file. Once the wg0.conf client configuration is passed to such container, all is needed is to instruct the other containers to use the “wireguard” client container’s network.

Doing this way, the chosen containers will be sharing the wireguard container’s network, thus tunneling out all the traffic.

The interesting config to set the container’s network is the following:

    network_mode: container:wireguard-container-name

Note: the wireguard container needs NET_ADMIN permissions. This adds some level of privileges to the container, which is to take into consideration.

Container exposed ports

One more thing to remember is that if your containers (not the wireguard one) expose any ports locally, you now need to define those ports exposed in the wireguard container.

Docker compose example

Copy your wg0.conf into ./wg.

Here is a docker compose example file that creates a qbittorrent container whose traffic will be completely tunneled out through the wireguard container.

The image linuxserver/wireguard has a “client” mode, which makes it act as a wireguard client when a wg0.conf file is supplied.

The container “qbittorrent” is sharing the wireguard container network, this is the magic bit that let the container’s traffic be tunneled out through the wireguard container.

---
version: "3.7"
services:
  qbittorrent-wg:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent-wg
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Etc/UTC
      - WEBUI_PORT=8080
    volumes:
      - ./config:/config
      - ./data:/downloads
    restart: unless-stopped
    network_mode: container:wireguard-my
  
  wireguard-my:
    image: linuxserver/wireguard
    container_name: wireguard-my
    restart: unless-stopped
    networks:
      - net01
    volumes:
      - './wg:/config'
      - '/lib/modules:/lib/modules:ro'
    environment:
      - PUID=1000
      - PGID=1000
    cap_add:
      - NET_ADMIN
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1

networks:
  net01:
    driver: bridge

This is all.

Health checks

If you want to make 100% sure the containers traffic is properly tunneled, you can create a compose health check script.

Since you know the VPN server IP address, you can make sure the container’s exit IP is the same with a health check.

Create health_check.sh as follows:

#!/bin/bash

# Health check function for a single container
function check_container_health() {

    local response
    response=$(curl -s ipinfo.io/ip)

    if [[ "$response" == "<INSERT VPN IP>" ]]; then
        echo "$container_name is healthy."
        exit 0
    else
        echo "$container_name is unhealthy."
        exit 1
    fi
}

# Health check for container
check_container_health

then share the file with the containers through a volume and set up the health check compose instruction.

Here is the updated compose file:

---
version: "3.7"
services:
  qbittorrent-wg:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent-wg
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Etc/UTC
      - WEBUI_PORT=8080
    volumes:
      - ./config:/config
      - ./data:/downloads
      - ./health_check.sh:/health_check.sh
    restart: unless-stopped
    network_mode: container:wireguard-my
    healthcheck:
      test: ["CMD", "/bin/bash", "/health_check.sh"]
      interval: 30s
      retries: 3
      timeout: 10s
  
  wireguard-my:
    image: linuxserver/wireguard
    container_name: wireguard-my
    restart: unless-stopped
    networks:
      - net01
    volumes:
      - './wg:/config'
      - '/lib/modules:/lib/modules:ro'
      - ./health_check.sh:/health_check.sh
    environment:
      - PUID=1000
      - PGID=1000
    cap_add:
      - NET_ADMIN
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
    healthcheck:
      test: ["CMD", "/bin/bash", "/health_check.sh"]
      interval: 30s
      retries: 3
      timeout: 10s
    ports:
      - 6081:6081
      - 7881:6881
      - 7881:6881/udp

networks:
  net01:
    driver: bridge


I hope you found this post helpful. If you have any questions or feedback, feel free to leave a comment below.


Last modified: 30 July 2023