Making a Docker Container Use a VPN – Natural Born Coder (2024)

A while back I wrote a post on routing all traffic through a VPN under Linux. The solution discussed in that post is fine if you are only dealing with regular applications but when you are dealing with containers the world is a difference place. Docker networks are, or at least can be, complicated. By default when a single container is started (e.g. with Docker run) it goes into the default bridge network. When you start a number of services with Docker Compose it will, by default, create a new bridge network for you with a name based on the name of your project.

If you are running a firewall similar to the one I discussed earlier you might expect that the containers started with Docker would also be forced to use that VPN but you’d be wrong. Docker creates it’s own chains in iptables that bypass the rules set up by UFW and worse UFW can’t even show you that has happened. Docker effectively bypasses the firewall you’ve created.

As an experiment, if you set up a completely closed off firewall with UFW and then start up a container with a port mapped to the host (let’s say port 80 for nginx) you’ll be able to access it from your network even though the port isn’t open on the firewall according to UFW. If you change the docker run command to –net=host then you’ll find the firewall works, this is because you’ve placed the container on the host network and forced it to use the rules UFW created. Likewise, if that container connects to the outside world using the default bridge it won’t got out over the VPN but it will if it’s set to use the host network. Why is this a problem? Let’s say you’re using Deluge to download Linux ISO’s. You don’t want anyone to know you like Linux so you’ll want that to run over a VPN.

Solution

One solution I explored was to just place all the containers on the host network and continue using the UFW firewall I had already configured. This would be a quick solution as it would require only a minimal change to the compose file but it’s also not a great solution. Using the host network is really intended for applications that must have this type of access, for example when running a DHCP server that requires access to MAC addresses. A much better solution is to run a separate container that opens and maintains a VPN link and then make the other containers use the networking stack of that VPN container. This type of configuration is not well documented in Docker, the best I could find was this.

There are a number of images available that will create a VPN container that you can use. At the most basic end is an OpenVPN container but if you are using Nord or PIA there are specific containers for those VPN providers (and others). All these containers work in the same way and are configured similarly and the configuration of the Nord service is shown below. This VPN container requires additional capabilities (cap_add) because it needs to manipulate the network in ways containers aren’t usually allowed to (see here). At the bottom of the configuration are the ports that will be exposed to the local network, note that this setting is different to the PORT environment variable which is for ports you want to expose over Nord.

nordvpn: image: bubuntux/nordvpn networks: pirate_net: # ipv4_address: 172.25.0.2 # Optionally give this a fixed IP address cap_add: - NET_ADMIN - SYS_MODULE sysctls: - net.ipv4.conf.all.rp_filter=2 devices: - /dev/net/tun environment: - USER=example@example.com - PASS=secret_password - CONNECT=France - TECHNOLOGY=NordLynx - NETWORK=192.168.1.0/24 ulimits: memlock: soft: -1 hard: -1 ports: - 7878:7878 # Radarr - 8112:8112 # Deluge - 9117:9117 # Jackett

This configuration differs slightly from the reference configuration given by the image maintainer. The main difference is that I have placed the VPN container in it’s own bridge network. This isn’t strictly necessary but I like to have a named network rather than just relying on the default. It also makes it easy to assign a fixed IP address if you need that for some reason. Importantly, the other containers that use this VPN container for internet access can use “localhost” to reach each other. This is because from a network perspective they all exist within the VPN container. What the other containers can’t do is reference each other by name as the Docker internal DNS system doesn’t work with this set up. In other words Radarr reaches Deluge using the localhost:8112 address.

The custom network configuration looks like this:

networks: pirate_net: driver: bridge # ipam: # driver: default # config: # - subnet: 172.25.0.0/24

For other containers use a configuration like this:

jackett: image: ghcr.io/linuxserver/jackett container_name: jackett restart: unless-stopped network_mode: service:nordvpn environment: - PUID=${PUID} - PGID=${PGID} - TZ=Europe/London - AUTO_UPDATE=true volumes: - ${JACKETT_CONFIG_DIR}:/config - ${BLACKHOLE_DIR}:/downloads depends_on: - nordvpn

Notice that this container depends on the VPN container, this is important as the VPN container must come up first. All containers in this compose should depend on the VPN container. Also notice that there are no ports defined here, this is because you can’t access this container directly. Access needs to be through the VPN container hence why they ports are mentioned there. Most importantly though notice that the network mode is set to “service:nordvpn”, this tells the container to use the VPN container for it’s network.

Notes

If the VPN container goes down (simulate with docker-compose stop nordvpn) then any container depending on it becomes unreachable and will need to be restarted if you want to connect to the internet again. The Nord image I’m using here seems to be stable though and will transparently restart the VPN if that goes down.

Health Checks

After running the VPN container for about 24 hours I noticed that my remote IP address had changed. I watched the service for a little while and I noticed the address changed a couple more times. I grep’ed the log files for the container and sure enough there were a few instances of the IP address changing. I’ve removed the actual IP addresses but the number at the end indicates a unique IP (note, I get double entries for most lines, the duplicates have been removed).

$ docker logs container_name | grep 'rule add to'2021/02/18 23:18:16 [nordlynx] ip -4 rule add to xxx.xxx.xxx.xx1 lookup main priority 327652021/02/18 23:28:14 [nordlynx] ip -4 rule add to xxx.xxx.xxx.xx2 lookup main priority 327652021/02/19 09:44:13 [nordlynx] ip -4 rule add to xxx.xxx.xxx.xx3 lookup main priority 327652021/02/19 10:04:15 [nordlynx] ip -4 rule add to xxx.xxx.xxx.xx4 lookup main priority 327652021/02/19 10:09:17 [nordlynx] ip -4 rule add to xxx.xxx.xxx.xx5 lookup main priority 32765

I started the service at 11:18 and it swapped IP at 11:28 but then was stable for nine hours before swapping IP in quick succession. After the 10:09 it seemed to become stable again. From what I can tell this is being caused by the healthcheck in the Dockerfile for the container. It attempts to check if the IP address you are using is protected and if it fails to return true it disconnects and reconnects your VPN. I’ll try turning on debugging later but for now it seems to be working even if it’s not as stable as it could be.

Additional Reading

I read a lot of pages to come up with my understanding of this problem, this is a list of just the ones that really helped me.

Making a Docker Container Use a VPN – Natural Born Coder (2024)

FAQs

How to use VPN in Docker container? ›

  1. Set Up OpenVPN on Docker Manually. Step 1: Create Data Volume. Step 2: Create OpenVPN Container. Step 3: Set up Certificates. Step 4: Start OpenVPN Container. Step 5: Generate Client Certificate. Step 6: Compile OpenVPN Configuration File. Step 7: Connect to Server.
  2. Set Up OpenVPN on Docker with DockOvpn.
Sep 14, 2023

How to get real IP address inside Docker container? ›

How to get the IP address of the docker host from inside a docker container?
  1. Run the following command to start a Docker container: Copied! docker run -it alpine /bin/sh. ...
  2. Once inside the container, run the following command to get the IP address of the Docker host: Copied! ping docker.for.mac.localhost.
Oct 5, 2023

Which of the below is shared by all the Docker containers? ›

Docker containers include the application and all of its dependencies. It shares the kernel with other containers, running as isolated processes in user space on the host operating system.

What are the various states that a Docker container can be in at any given point in time? ›

Let's now deep dive into each of these states:
  • 3.1. Created. Docker assigns the created state to the containers that were never started ever since they were created. ...
  • 3.2. Running. ...
  • 3.3. Restarting. ...
  • 3.4. Exited. ...
  • 3.5. Paused. ...
  • 3.6. Dead.
Mar 19, 2024

How do you allow a docker container to access the network? ›

Connect a container to a network when it starts

You can also use the docker run --network=<network-name> option to start a container and immediately connect it to a network.

Does a docker container get its own IP address? ›

By default, the container gets an IP address for every Docker network it attaches to. A container receives an IP address out of the IP subnet of the network. The Docker daemon performs dynamic subnetting and IP address allocation for containers. Each network also has a default subnet mask and gateway.

How to assign IP address in docker container? ›

How to Provide the Static IP to a Docker Container?
  1. Docker is an open-source project that makes it easier to create, deploy and run applications. ...
  2. Solution:
  3. Step 1: Configure the Docker in the Server.
  4. Step 2: Create a Network with Subnet (CIDR).
  5. Step 3: Deploy the Container this Network with your Custom Static IP.
  6. Output:
Mar 30, 2023

How to get the IP address of a container in docker? ›

Summary
  1. Use docker ps to list containers.
  2. Use docker inspect <container_id_or_name> to get detailed information.
  3. Extract the IP address using grep or docker inspect -f .
Sep 8, 2024

What are the 4 states of Docker container? ›

status
StatusDescription
createdA container that has never been started.
runningA running container, started by either docker start or docker run .
pausedA paused container. See docker pause .
restartingA container which is starting due to the designated restart policy for that container.
3 more rows

What is the difference between Docker image and Docker container? ›

A Docker container is a self-contained, runnable software application or service. On the other hand, a Docker image is the template loaded onto the container to run it, like a set of instructions. You store images for sharing and reuse, but you create and destroy containers over an application's lifecycle.

Where can docker be deployed? ›

You can deploy public images from any registry Render can reach, and you can deploy private images from the following registries:
  • Docker Hub.
  • GitHub Container Registry.
  • GitLab Container Registry.
  • Google Artifact Registry.

Where do docker containers go? ›

On Linux, all Docker-related data, including images, containers, volumes, and networks, is stored within the /var/lib/docker directory, known as the root directory. The exact directory inside the root directory where images are stored depends on the storage driver being used.

What happens when a container is exited? ›

paused - If a container is paused, it is temporarily stopped from running its processes, but it is not terminated. exited - If a container's main process completes, the container stops and transitions to the exited state. dead - If a container fails to start, it is in the dead state.

How to use docker container as proxy? ›

Instead of configuring the Docker client, you can specify proxy configurations on the command-line when you invoke the docker build and docker run commands. Proxy configuration on the command-line uses the --build-arg flag for builds, and the --env flag for when you want to run containers with a proxy.

How to tunnel into a docker container? ›

Docker ssh tunnel
  1. Expose the port within your Docker container to the remote server. ``` # docker.example.com.yaml. ...
  2. Deploy.
  3. On your machine, make a tunnel: ``` ssh -NL 3001:localhost:6543 root@$(docker-machine ip docker.example.com) ...
  4. Use psql from your machine.
  5. Remove from docker. example.com.

How to install NordVPN on docker? ›

Building the NordVPN Docker image
  1. Build the image with this command (add your desired image name): sudo docker build -t <image name> . ...
  2. Run the image: sudo docker run -it --hostname mycontainer --cap-add=NET_ADMIN --sysctl net.ipv6.conf.all.disable_ipv6=0 <image name> ...
  3. Login to NordVPN using a token method:

How to use host network in docker? ›

The host networking driver only works on Linux hosts, but is available as a beta feature on Docker Desktop version 4.29 and later for Mac, Windows, and Linux. To enable this feature, navigate to the Features in development tab in Settings, and then select Enable host networking.

References

Top Articles
Latest Posts
Recommended Articles
Article information

Author: Reed Wilderman

Last Updated:

Views: 5903

Rating: 4.1 / 5 (72 voted)

Reviews: 95% of readers found this page helpful

Author information

Name: Reed Wilderman

Birthday: 1992-06-14

Address: 998 Estell Village, Lake Oscarberg, SD 48713-6877

Phone: +21813267449721

Job: Technology Engineer

Hobby: Swimming, Do it yourself, Beekeeping, Lapidary, Cosplaying, Hiking, Graffiti

Introduction: My name is Reed Wilderman, I am a faithful, bright, lucky, adventurous, lively, rich, vast person who loves writing and wants to share my knowledge and understanding with you.