Below article will explain how to configure OpenVPN container to make it work with any VPN provider. Next I will reconfigure one of the existing container from the stack to make it use a VPN network as its gateway - (in case of VPN connection dropping containers using VPN gateway should also loose access the Internet). Next I will make container behind VPN gateway reachable from withing the local network. In the last step I will do a few tests to check if VPN is actually working and if traffick goes the way we expect it to go.
VPN Container - dperson/openvpn-client
This container, among the others is one of the few that is actually a VPN client implementation, majority of the VPN containers available are server ones - not the ones you need. I did not create this container, I only describe its usability in Docker in LMDS stack. This guide can be adopted to any Docker stack not just to LMDS. Please be aware that in case of issues with VPN connectivity, container workability or accessability overall, I wont be able to help you diagnose the problem. At this level of complexity you have to spend some time and educate yourself about the tools you are about to use. It is not that, I do not want to help but this might be extremely difficult and very frustrating for both of us on this subject. This is also why I did not attempt to script this procedure as this would be impossible to get right for everyone.
I hope you are successful while following this instruction.
While you inside LMDS folder run:
./deploy.sh
Choose "Build LMDS Stack" and then select "vpn-client" from the list and OK, hit Enter couple of times confirming default options, when you are back to the shell run:
docker-compose up -d
After "vpn-client" container is created, edit section of the docker-compose.yml
shown below:
vpn:
container_name: vpn-gateway
image: dperson/openvpn-client
restart: unless-stopped
ports: # List all port numbers of the containers that you would like to put behind VPN.
# Remember, these ports can only exist in a single place inside entire docker-compose.yml file.
- 90:80 # Redirecting to port 90 as 80 we will need this at some point for reverseproxy traefik.
dns: # Use VPN provided DNS IPs if you have them otherwise leave as is.
- 8.8.8.8
- 8.8.4.4
cap_add:
- NET_ADMIN
devices:
- '/dev/net/tun:/dev/net/tun:rwm'
environment:
FIREWALL: '' # If you use different VPN ports then default 1194 add them in here, otherwise leave it empty as is.
#VPN 'server_address;user;password;port' # Configure VPN server address, authentication and port if needed by your VPN provider (port value is optional) we will use an external config file for this, leave commented as is.
PUID: 1000
PGID: 1000
TZ: UTC+0
ROUTE: '192.168.0.0/16' # Specify subnet of your home LAN in order to connect into the container behind VPN - if you don't, containers will work but you wont be able to connect to them locally.
networks:
- default
read_only: true
tmpfs:
- /run
- /tmp
security_opt:
- label:disable
stdin_open: true
tty: true
volumes:
- ./vpn:/vpn # This folder should contain two files:
# 1. Copy .ovpn file you received from VPN provider in here and rename it to vpn.conf
# 2. Create vpn.auth file and put inside your VPN access username and password in two separate lines one under another.
# Important: edit vpn.conf file you renamed and find line called auth-user-pass append it with a path to your vpn.auth file, in my case: auth-user-pass /vpn/vpn.auth
There is few things you have to adjust in above example:
ports:
add all port numbers of the containers that you are planing to put behind VPN in this section, in example if you plan to hide deluge container behind VPN, you should add deluge container exposed ports like 8112 and 58846 in here and remove them from deluge container definition itself. These ports can only exist in one place inside docker-compose.yml
file. For the containers like Portainer on Nginx that should not go through VPN, keep their port numbers under container declaration ony as they initially are.dns:
You can leave as is or use VPN provided DNS IPs - if you have them. Before changing them, test with Google ones first.FIREWALL: ''
if you use custom VPN ports (not default 1194) added here and under VPN
section, otherwise leave it empty as is.VPN
Configure VPN server address, authentication and port if needed by your VPN provider (port value is optional) we will use an external config file for this, leave commented out as is.ROUTE: '192.168.0.0/16'
this value is adding a static route inside the VPN container. Provide a subnet that reflect your home LAN addressing. This is needed in order to reach containers connected to the VPN from your local network - if you misconfigure this part, containers will work, but you wont be able to access them from your laptop or PC in local network. You will still be able to access containers hidden behind VPN using host IP address and port exposed for the container as before. ./vpn:/vpn
This folder should contain two files:
.ovpn
file you received from VPN provider in here and rename it to vpn.conf
vpn.auth
file and put there your username and password in two separate lines one under another.vpn.conf
file you renamed and find line called auth-user-pass append it with a path to your vpn.auth
file, in my case: auth-user-pass /vpn/vpn.auth
All what you have to do in order for VPN gateway to utilize free VPN provider services is to copy *.ovpn
file in to ~/LMDS/vpn/
folder and restart the container. You can grab some example files from vpngate.net. After you restart VPN container give it a minute before testing connectivity, it might take a while before VPN tunnel is established. On this occasion I did not even had to rename *.ovpn
to *.conf
and all worked ok.
Utilizing VPN provider, that requires you to have an account and paid subscription is little more involved. Below is what I had to do to make it work - it wasn't as straight forward as I though it could be. Container I selected for this project can utilize any VPN provider config with little persuasion and it has some additional features that other containers might not.
What needs to be done:
vpn.auth
file inside ~/LMDS/vpn/
folder. vpn.auth
file should contain VPN access username and password in two separate lines. Please double check with your VPN provider, what username and password you should use while connecting manually using OpenVPN as these credentials might be different from the ones you use with their app. ProtonVPN has i.e. one set of credentials for dedicated app they provide and completely different set of credentials for manual connection from OpenVPN.
*.ovpn
file in to ~/LMDS/vpn/
and rename it to vpn.conf
i.e. nl-free-07.protonvpn.com.udp.ovpn
rename to vpn.conf
. Inside that file find a line "auth-user-pass" - it should be there somewhere, if not there add it manually "auth-user-pass /vpn/vpn.auth". This will provide credentials for VPN tunnel to be made by OpenVPN software inside the Docker container.
This should be all, but in my case I had to remove one section from the vpn.conf
file, that was causing a problem.
When I initially started VPN container using "docker-compose up -d vpn"
container was rebooting itself over and over again, I checked the logs "docker-compose logs vpn"
and saw below message popping up after each reboot of the container:
Options error: --up script fails with '/etc/openvpn/update-resolv-conf': No such file or directory (errno=2)
From inside vpn.conf
I had to remove below three lines in order to keep VPN container up:
script-security 2
up /etc/openvpn/update-resolv-conf
down /etc/openvpn/update-resolv-conf
After removing above, container started ok and successfully connected to ProtonVPN server:
vpn-gateway | 2021-06-08 20:25:41 TUN/TAP device tun0 opened
vpn-gateway | 2021-06-08 20:25:41 /sbin/ip link set dev tun0 up mtu 1500
vpn-gateway | 2021-06-08 20:25:41 /sbin/ip link set dev tun0 up
vpn-gateway | 2021-06-08 20:25:41 /sbin/ip addr add dev tun0 10.30.0.9/16
vpn-gateway | 2021-06-08 20:25:41 /sbin/ip route add 185.107.80.216/32 via 172.18.0.1
vpn-gateway | 2021-06-08 20:25:41 /sbin/ip route add 0.0.0.0/1 via 10.30.0.1
vpn-gateway | 2021-06-08 20:25:41 /sbin/ip route add 128.0.0.0/1 via 10.30.0.1
vpn-gateway | 2021-06-08 20:25:41 Initialization Sequence Completed
If you previously deployed any container you should find it definition somewhere inside docker-compose.yml
file.
i.e. initially deluge container will be defined as shown below:
deluge:
image: linuxserver/deluge
container_name: deluge
stdin_open: true
tty: true
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
- UMASK=022 #optional
volumes:
- ./volumes/deluge/config:/config
- ./downloads:/downloads
ports:
- 8112:8112
- 58846:58846
We will change it as follows:
deluge:
image: linuxserver/deluge
container_name: deluge
depends_on: # Add dependency on vpn container, this container won't start if vpn container is not running.
- vpn
network_mode: service:vpn # This line will put container behind VPN one.
stdin_open: true
tty: true
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/London
- UMASK=022 #optional
volumes:
- ./volumes/deluge/config:/config
- ./downloads:/downloads
# ports: # Comment out all the ports you had exposed before and add them to the vpn container definition underneath PORTS section.
# - 8112:8112
# - 58846:58846
depends_on: - vpn
adding dependency on vpn container, container having this flag wont start until vpn container is up.network_mode: service:vpn
adding this line will make particular container seat behind VPN gateway and pass its traffick through VPN provider network. ports:
Comment out all the ports you had exposed before and add them to the vpn container definition underneath PORTS section instead.
There are two mandatory things that I hope you already did in order to access your VPN protected containers from local network. Inside docker-compose.yml
locate vpn-client
container definitions and make sure that you exposed ports of the containers you linked with vpn-client under that container, also make sure ROUTE section has your local network subnet configured properly. I would imagine that what is set up there by default would be correct for most of the cases. If you have different subnet - more likely you know what you are doing anyway.
in order to put deluge container behind VPN I will add deluge ports to vpn-client container as shown below:
vpn:
container_name: vpn-client
image: dperson/openvpn-client
restart: unless-stopped
ports: # List all port numbers of the containers that you would like to put behind VPN. Remember, these ports can only exist in a single place inside entire docker-compose.yml file.
- 90:80 # Redirecting to port 90 as 80 we will need this at some point for reverseproxy traefik.
- 8112:8112
- 58846:58846
dns: # Use VPN provided DNS IPs if you have them otherwise leave as is.
- 8.8.8.8
- 8.8.4.4
cap_add:
- NET_ADMIN
devices:
- '/dev/net/tun:/dev/net/tun:rwm'
environment:
FIREWALL: '' # If you use different VPN ports then default 1194 add them in here, otherwise leave it empty as is.
#VPN 'server_address;user;password;port' # Configure VPN server address, authentication and port if needed by your VPN provider (port value is optional) we will use an external config file for this, leave commented as is.
PUID: 1000
PGID: 1000
TZ: UTC+0
ROUTE: '192.168.0.0/16' # Specify subnet of your home LAN in order to connect into the container behind VPN - if you don't, containers will work but you wont be able to connect to them locally.
networks:
- default
read_only: true
tmpfs:
- /run
- /tmp
security_opt:
- label:disable
stdin_open: true
tty: true
volumes:
- ./vpn:/vpn # This folder should contain two files:
# 1. Copy .ovpn file you received from VPN provider in here and rename it to vpn.conf
# 2. Create vpn.auth file and put inside your VPN access username and password in two separate lines one under another.
# Important: edit vpn.conf file you renamed and find line called auth-user-pass append it with a path to your vpn.auth file, in my case: auth-user-pass /vpn/vpn.auth
If you did everything as instructed, save the changes and you should be ready to recreate stack by executing docker-compose up -d
from the command line.
Do not forget to put your .ovpn file inside ~/LMDS/vpn/ folder, rename it to vpn.conf, then append auth-user-pass line providing path to your credentials file called vpn.auth also created and stored inside this folder.
In order to test if container hidden behind vpn gateway use VPN network and if is using VPN provider network as its default route we will need to deploy another container. This container has all the testing tools we need to find out the truth. We could use deluge for testing or any other container you stashed behind vpn-gateway but I am sure they do not even have ifconfig command available.
I found below container very useful for checking the network related features, you can use any other if you like.
Copy below container definition in to docker-compose.yml file - it can go at the very bottom of that file, save it and run docker-compose up -d
again.
netmtool:
container_name: net-tool
image: praqma/network-multitool
depends_on:
- vpn
network_mode: service:vpn
When net-tool is deploy check if is running docker ps
. if you see it running enter the container command line docker exec -it net-tools /bin/bash
or docker exec -it net-tools /bin/sh
your prompt will change to something like this bash-5.0#
or just #
When inside the container run ifconfig
and check if you see tun0
interface and notice its IP address - do you see IP that belongs to your VPN provider - you should.
Example:
tun0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
inet addr:10.211.1.149 P-t-P:10.211.1.150 Mask:255.255.255.255
UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1500 Metric:1
RX packets:409055 errors:0 dropped:0 overruns:0 frame:0
TX packets:7550 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:500
RX bytes:129422927 (123.4 MiB) TX bytes:679034 (663.1 KiB)
Next, we can check default route to the Internet - type route
First record you see called default should point to IP address that belongs to the VPN provider.
Example:
bash-5.0# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 10.211.1.150 128.0.0.0 UG 0 0 0 tun0
default 172.18.0.1 0.0.0.0 UG 0 0 0 eth0
10.211.1.150 * 255.255.255.255 UH 0 0 0 tun0
23.170.32.37 172.18.0.1 255.255.255.255 UGH 0 0 0 eth0
128.0.0.0 10.211.1.150 128.0.0.0 UG 0 0 0 tun0
172.18.0.0 * 255.255.0.0 U 0 0 0 eth0
192.168.0.0 172.18.0.1 255.255.0.0 UG 0 0 0 eth0
192.168.100.0 172.18.0.1 255.255.255.0 UG 0 0 0 eth0
As we know IP address of the tun0 and we checked that our default gateway IP belongs to our VPN provider network, we can now verify if traffick actually goes the wey we expect it to go - through the VPN pipe mtr google.com
it will show you a traceroute like output but more dynamic, look at the first IP there - is it your VPN provider broadcast IP? In my case it start from 10.211 .... so it is ok and it works. I can reach Google and traffick goes over VPN network.
Example:
c48a046e12c7 (10.211.1.149) 2021-03-22T22:05:20+0000
Keys: Help Display mode Restart statistics Order of fields quit
Packets Pings
Host Loss% Snt Last Avg Best Wrst StDev
1. 10.211.254.254 0.0% 12 309.8 301.2 181.9 388.9 63.9
2. vl99-dist1-van1.etinw.net 0.0% 12 215.1 264.9 172.3 418.4 67.8
3. 1gig-e1-4-fw1-van1.etinw.net 0.0% 12 317.0 300.5 225.2 332.7 35.4
4. xe-0-1-3-0-border2-van1.etinw.net 0.0% 12 222.7 266.2 206.5 367.7 62.0
5. xe-1-0-11.mpr1.yvr3.ca.zip.zayo.com 0.0% 11 286.4 271.6 194.5 328.9 37.0
6. ae7.cs1.sea1.us.zip.zayo.com 0.0% 11 200.7 214.7 164.9 301.6 40.3
7. ae27.mpr1.sea1.us.zip.zayo.com 0.0% 11 259.2 286.8 197.4 495.7 80.2
8. 72.14.208.172 0.0% 11 170.5 282.1 170.5 392.9 81.9
9. 74.125.243.193 0.0% 11 246.6 286.7 181.3 506.6 81.8
10. 209.85.254.249 0.0% 11 346.5 340.5 222.7 516.5 78.0
11. sea30s02-in-f14.1e100.net 0.0% 11 252.8 278.0 187.5 412.8 64.1
Any container having network_mode: service:vpn
line in its definition will now work exact the same way. You can also test it by removing that line from "net-tool" container, save updated docker-compose.yml file, running docker-compose up -d
again and repeat above tests - this time, without network_mode: service:vpn
line present traffick will bypass VPN protection.
With your support anything is possible