Put a docker container behind VPN

Navratan Lal Gupta
Linux Shots
Published in
5 min readAug 29, 2022

Sometime you might need to put the application container behind a VPN without putting host server behind it. Reasons can be anything, If application need to access region restricted service, Or for preventing application from prying eyes of ISPs, Or If you are using a BitTorrent client, its always recommended to put it behind VPN.

Here, We will know how can we put an application running in a docker container, behind a VPN.

This is done using a docker networking concept where we use network interface of another container into application container.

Here is the concept behind it.

  1. A VPN client’s container runs as docker container. It connects to VPN servers provided by VPN providers.
  2. An application container runs using network interface of VPN client’s container.

In this way, All the communication made by application container to internet or from internet goes to through VPN container, hence, the VPN network.

Diagrammatic representation of setup

Here in demo, I will be using ProtonVPN’s free VPN service. And exampless of using it using NordVPN and ExpressVPN will also be shared.

Pre-Requisite

I have performed this demo on:

  • Ubuntu 22.04 server
  • Docker 20.10.17 root mode
  • Docker Compose version v2.6.0

Docker should NOT be running in rootless mode. Since, VPN container need network capability.

Demo

In this demo, I will be deploying transmission container which is a BitTorrent client, behind VPN.

  1. Get OpenVPN username and password from protonvpn account. Same way username and passsword can be obtained from NordVPN or ExpressVPN account.

2. Deploy docker container of VPN client.

Keep a note of application container ports which needs to be published to hosts.

Those ports will be published in VPN’s container instead of application’s container.

Here, We will be using gluetun which is a opensource universal VPN client for number of VPN providers. Check about it here: https://github.com/qdm12/gluetun

docker run -d --name vpn \
--cap-add=NET_ADMIN \
--env VPN_SERVICE_PROVIDER=protonvpn \
--env OPENVPN_USER=zxxxxxxxxxxxxxxxf \
--env OPENVPN_PASSWORD=Lxxxxxxxxxyyyyyxxxxn \
--env SERVER_COUNTRIES=Netherlands \
--env FREE_ONLY=on \ # Only required when using ProtonVPN's free service
--network=mynetwork \
-p 9091:9091 -p 51413:51413 -p 51413:51413/udp \ # Application container's ports to be published
qmcgaw/gluetun

Here,

VPN_SERVICE_PROVIDER is variable used to provide VPN provider’s name. Valid values are protonvpn, nordvpn, expressvpn, surfshark and many others. Check the link provided above.

OPENVPN_USER is username extracted from VPN account.

OPENVPN_PASSWORD is password extracted from VPN account.

SERVER_COUNTRIES is the country to which VPN to be connected. Country must be from provided service by VPN provider.

SERVICE_REGION is the name of region to which VPN to be connected. This must be supported by VPN provider.

FREE_ONLY this variable is only used with ProtonVPN and is only required when using free service of ProtonVPN.

3. Deploy application container

docker run -d --name transmission \
--network=container:vpn \
--env USER=admin \
--env PASS=nimdatx \
lscr.io/linuxserver/transmission

If you noticed, there is no port mapping added here. Its because it must only be added to VPN container.

--network=container:vpn Adds application container to same network interface as of vpn container. So, IP address of both containers will be same.

4. Test the IP address

docker exec -it transmission bash
curl ipinfo.io/json

Once you run above command inside transmission container, It should show IP address and region to which VPN is connected.

When IP address is queried from Transmission container

I am based in India, but Transmission container is behind VPN connected to Netherlands/Amsterdam.

When IP address is queried from host machine

5. Access the application

Application can be accessed using public IP address (or local ip if hosted on same system) of server and port.

For use with reverse proxy or internal container to container communication, VPN’s container name (service name, in case of docker compose) should be used instead of application’s container/service name.

e.g. If trying to add reverse proxy using nginx

location \ {
proxy_pass http://vpn:9091;
# proxy_pass http://container_name_of_vpn:portnumber
# NOT, proxy_pass http://transmission:9091;
}

The complete setup can also be deployed using docker compose with below docker-compose.yml file.

version: "3.9"
name: media-stack
services:
vpn:
container_name: vpn
image: qmcgaw/gluetun
cap_add:
- NET_ADMIN
environment:
- VPN_SERVICE_PROVIDER=protonvpn
- OPENVPN_USER=zxxxxxxxxxxxxxxx
- OPENVPN_PASSWORD=Lxxxxxxxxxyyyyyxxxxn
- SERVER_COUNTRIES=Netherlands
- FREE_ONLY=on
networks:
- mynetwork
ports:
- 9091:9091
- 51413:51413
- 51413:51413/udp
restart: "unless-stopped"
transmission:
container_name: transmission
image: lscr.io/linuxserver/transmission
network_mode: service:vpn
environment:
- USER=admin
- PASS=nimdatx
restart: "unless-stopped"
networks:
mynetwork:
external: true

Examples for other VPN providers

Get OpenVPN username and password from respective VPN provider’s account page.

To get list of VPN provider’’s support and supported countries and regions, visit, https://raw.githubusercontent.com/qdm12/gluetun/master/internal/storage/servers.json

NordVPN:

version: "3.9"
name: media-stack
services:
vpn:
container_name: vpn
image: qmcgaw/gluetun
cap_add:
- NET_ADMIN
environment:
- VPN_SERVICE_PROVIDER=nordvpn
- OPENVPN_USER=zxxxxxxxxxxxxxxx
- OPENVPN_PASSWORD=Lxxxxxxxxxyyyyyxxxxn
- SERVER_REGIONS=Switzerland
networks:
- mynetwork
ports:
- 9091:9091
- 51413:51413
- 51413:51413/udp
restart: "unless-stopped"
transmission:
container_name: transmission
image: lscr.io/linuxserver/transmission
network_mode: service:vpn
environment:
- USER=admin
- PASS=nimdatx
restart: "unless-stopped"
networks:
mynetwork:
external: true

ExpressVPN:

version: "3.9"
name: media-stack
services:
vpn:
container_name: vpn
image: qmcgaw/gluetun
cap_add:
- NET_ADMIN
environment:
- VPN_SERVICE_PROVIDER=expressvpn
- OPENVPN_USER=zxxxxxxxxxxxxxxx
- OPENVPN_PASSWORD=Lxxxxxxxxxyyyyyxxxxn
- SERVER_COUNTRIES=Netherlands
networks:
- mynetwork
ports:
- 9091:9091
- 51413:51413
- 51413:51413/udp
restart: "unless-stopped"
transmission:
container_name: transmission
image: lscr.io/linuxserver/transmission
network_mode: service:vpn
environment:
- USER=admin
- PASS=nimdatx
restart: "unless-stopped"
networks:
mynetwork:
external: true

To know more about, Gluetun VPN client and supported VPN providers, Please visit, https://github.com/qdm12/gluetun/wiki

To setup your own self-hosted media stack, Please read, https://medium.com/linux-shots/self-host-media-stack-jellyfin-radarr-sonarr-jackett-transmission-3e6a0adf716e?source=friends_link&sk=9e668f8d9da86a61102a324fe360474f

You may also support me by offering a cup of coffee, https://www.buymeacoffee.com/linuxshots

Thanks

Navratan Lal Gupta

Linux Shots

--

--

Navratan Lal Gupta
Linux Shots

I talk about Linux, DevOps, Kubernetes, Docker, opensource and Cloud technology. Don't forget to follow me and my publication linuxshots.