Using openvpn-client with Docker

28 Jan 2022 in Infrastructure

I recently worked out the correct incantation to get a set of containers to connect to the internet via a VPN using docker-compose. I run it on a QNAP NAS, but it should work on any Linux-like system (I couldn’t get it working on MacOS).

The following docker-compose.yml will create two containers - one to run the VPN client and a second that runs curl. I use this to run curl to check that the container has connectivity and the VPN IP address is returned.

version: "3"
container_name: vpn
image: dperson/openvpn-client:latest
- net_admin
restart: unless-stopped
- /dev/net/tun:/dev/net/tun
- ./vpn-config:/vpn # You'll need to provide this
- label:disable
- 8001:8001
- 8002:8002
- 8003:8003
- bridge_vpn
entrypoint: ["/sbin/tini", "--", "/usr/bin/", "-d"]
image: alpine/curl
container_name: curl
restart: unless-stopped
network_mode: service:vpn

This compose file will expose ports 8001, 8002 and 8003 from any containers using network_mode: service:vpn and make them accessible via a bridge network. This is useful when running a service that connects to the internet using a VPN. (There are no exposed ports in this demo, but I wanted to make a note here as in my actual deployment some of the other services expose ports.)

The biggest change to this configuration compared to a lot of guides out there is the entrypoint line for the vpn service. The openvpn-client image supports a -d flag that adds some DNS related pre/post scripts. I found that these are required to make connectivity work via the VPN.

You may have noticed the vpn-config folder being mounted. This is where you’ll provide your VPN configuration and authentication files. I tested this with Private Internet Access. If you’re a PIA customer, you’ll need to create a file named vpn.auth with your PIA username on the first line, and password on the second line, plus a vpn.conf file with the following contents:

dev tun
proto udp
remote 1198
resolv-retry infinite
# persist-tun # disable to completely reset vpn connection on failure
cipher aes-128-cbc
auth sha1
remote-cert-tls server
auth-user-pass /vpn/vpn.auth # to be reachable inside the container
verb 1
reneg-sec 0
crl-verify /vpn/crl.rsa.2048.pem # to be reachable inside the container
ca /vpn/ca.rsa.2048.crt # to be reachable inside the container
keepalive 10 30 # send a ping every 10 sec and reconnect after 30 sec of unsuccessfull pings
pull-filter ignore "auth-token" # fix PIA reconnection auth error that may occur every 8 hours

This configuration connects to Sweden, but you can switch to any endpoint that you like. You’ll also need to download crl.rsa.2048.pem and ca.rsa.2048.pem from the PIA site.

Once you have everything configured, it’s time to create the containers by running docker-compose up -d. Once that’s complete, you can test your connection by logging in to the vpn service and making a HTTP call:

docker-compose exec vpn bash -c "curl"

If the above command returns an IP address successfully, you can also test it using the curl container which is configured to use the VPN for all network connectivity:

docker-compose run curl

At this point, you have a docker-compose setup that connects all of the containers configured via an OpenVPN connection. Replace the curl service with any other service you may want to run behind a VPN and enjoy as your traffic is safe from snooping eyes.