This post is going to be short and to the point, but first here’s some background.

I have a home server where I host a lot of services that I like to access externally, and where I also do some activity I’d like to be sent through a VPN. While I could route all my traffic through the VPN, I’d end up slowing down my Plex server and just complicate things unnecessarily. This is why I wanted to set up a fast VPN that only routes traffic from specific docker containers… and honestly I just wanted to learn how to use Wireguard!

I spent a long time trying to figure this out and though I’d share the entire process. While this isn’t an entirely common use case, I think it is incredibly useful and was actually pretty hard for me to find the answer.

I’m not really going to get into installing and getting a basic config working with Wireguard, just the steps after that are needed to get the specific setup working.

Install Wireguard on the server

I’ll be using a VPS with Ubuntu 18.04 installed which makes this pretty simple. I used this tool Wireguard-Install which supports Ubuntu, Debian, Fedora, CentOS and Arch. Simply follow the instructions and you’ll get a server config setup and a client config that you can use on your server.

You can then add a client and add the config that is spit out to /etc/wireguard/wg0.conf on your home server/client. To test that Wireguard is working correctly run wg-quick up wg0 to test it out. You should notice that all of your traffic is being routed to the VPN! Now let’s work on getting just a specific service going through.

Assign a static IP to our docker container

To identify our docker container we’ll give it a static internal IP which we will use later to identify traffic coming from it to redirect it. This simple update to your docker-compose will be enough.

Note: you can choose whatever subnet you want here

networks:
  static-network:
    ipam:
      config:
        - subnet: 192.168.104.0/28

# ...

services:
  transmission:

#   ...

    networks:
      static-network:
        ipv4_address: 192.168.104.5
docker-compose.yml

Great, now our docker container will have a static ip address, but don’t apply this just yet! Wireguard must be setup first for the traffic to correctly start routing through the interface we are about to create.

Edit the Wireguard client config to route specific traffic to the server

Let’s update our Wireguard config to route specific traffic coming to and from our container.

[Interface]
PrivateKey = <omitted-pvkey>
Address = 192.168.4.5/32
Table = off

PostUp = ip rule add from 192.168.104.5 table 42; ip route add default dev %i table 42;
PostDown = ip rule del from 192.168.104.5 table 42;

[Peer]
PublicKey = <omitted-pubkey>
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = <omitted-wg-server-ip>:51820
PersistentKeepalive = 25

The important bits are that PostUp and PostDown. These are commands that run before and after the WG interfaces comes up and goes down (Up generally sets up some routing rules and Down cleans them). We are creating a table here for all traffic coming from our ip, and setting the interface for that table to be our Wireguard interface (%i maps to the name of the interface, wg0 in our case).

Starting everything up

Now that everything is configured, we first need set up our Wireguard interface and make sure it is connected and healthy.

:$ sudo wg-quick up wg0
:$ sudo wg
interface: wg0
  public key: <ommitted-pbkey>
  private key: (hidden)
  listening port: 55938

peer: <ommitted>
  endpoint: <ommitted-server-ip>:51820
  allowed ips: 0.0.0.0/0, ::/0
  latest handshake: 1 minute, 24 seconds ago
  transfer: 4.64 MiB received, 109.40 MiB sent
  persistent keepalive: every 25 seconds

And now we can start up our docker service with docker-compose

:$ docker-compose up -d

And finally we can check to see that traffic is being routed through the VPN.

:$ docker exec -it transmission /bin/bash
root@465bfc97bf90:$ curl ifconfig.me
<ommitted-server-ip>