Search code examples
dockernetwork-programmingdocker-composeredisvpn

Use Gluetun VPN with Single Container in Docker Compose while still accessing dependent services


Question Edited for simplicity and generalization as I've found the answer.

I'm trying to incorporate a Gluetun VPN container into my existing application that does a large array of fairly network heavy tasks. I'd like to connect a single container (a specific Queue worker for example) to use a VPN for only a single task which improves reliability drastically. I had much trouble figuring out the networking side of it and how to do this, as when you connect a container to Gluetun with network_mode: service:gluetun", It looses docker-compose's DNS functionality, and the ability to resolve the names of other services, giving an application error when connecting to, for example a Redis Container or Database container.

I wanted my existing networking to remain as it was, with only the specific containers using the VPN for external connections, while the other containers used the host server's network as they always have been. How to resolve this issue and setup in this manner?


Solution

  • There are 2 ways to solve this, But only one way solves it in the way I wanted.

    BEST WAY

    To fully solve my problem, I ended up giving my dependent services a static IP, and using using the extra_hosts: Docker-compose tag to add these services to the gluetun container's /etc/hosts file, which allows the gluetun container to resolve the service names, which the other containers use Docker's DNS. I also added the FIREWALL_OUTBOUND_SUBNETS environment variable to the gluetun container with the same subnet as the network. I'm not 100% certain if this is required, But I did so based on the comment that helped me resolve the answer and it worked. Here's the docker-compose file :

    version: '3'
    services:
      redis-service:
        image: redis
        ports:
        - "6379:6379"
        networks:
          primary-network:
            ipv4_address: 172.10.0.2 # Static IP address for service in subnet 
    
      default-queue-service:
        image: queue
        depends_on:
          - redis-service
        environment:
          CONTAINER_ROLE: Default-Queue-worker
        networks:
          - "primary-network" # Uses primary network, External requests use host/server
    
      vpn-queue-service:
        image: queue
        depends_on:
          - redis-service
        environment:
          CONTAINER_ROLE: VPN-Queue-worker
        network_mode: "service:gluetun" # Uses Gluetun's network, external requests use VPN
    
      gluetun-service:
        image: qmcgaw/gluetun
        hostname: gluetun
        cap_add:
          - NET_ADMIN
        devices:
          - /dev/net/tun:/dev/net/tun
        ports:
          - 8888:8888/tcp # HTTP proxy
          - 8388:8388/tcp # Shadowsocks
          - 8388:8388/udp # Shadowsocks
    #    volumes:
    #      - /yourpath:/gluetun
        environment:
          # See https://github.com/qdm12/gluetun-wiki/tree/main/setup#setup
          - VPN_SERVICE_PROVIDER=<PROVIDER>
          - VPN_TYPE=openvpn
          # OpenVPN:
          - OPENVPN_USER=<REDACTED>
          - OPENVPN_PASSWORD=<REDACTED>
          # Wireguard:
          # - WIREGUARD_PRIVATE_KEY=<REDACTED>
          # - WIREGUARD_ADDRESSES=<REDACTED>
          # Timezone for accurate log times
          - TZ=
          # Server list updater
          # See https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-the-vpn-servers-list
          - UPDATER_PERIOD=
          - SERVER_COUNTRIES=
          - SERVER_REGIONS=
          - FIREWALL_OUTBOUND_SUBNETS=172.10.0.0/16 # Required for accessing subnet ips (i think)
        depends_on:
          - redis-service
        extra_hosts:
          - "redis-service:172.10.0.2" # Adds redis-service now static ip to gluetun's /etc/hosts for name resolution instead of DNS
        networks:
          - "primary-network" # Gluetun needs to be on the same network as other services
    networks:
      primary-network:
        ipam:
          config:
            - subnet: 172.10.0.0/16 # Defined static subnet ip. Make sure other docker networks do not conflict or it will error.
    
    

    OTHER WAY

    This was the standard answer I found most often while searching for this. This method consists of :

    1. Connecting network_mode: "service:gluetun" to ALL services
    2. moving All port mappings from the services, into the gluetun container.
    3. changing the Application Environment Host Name variables from, for example "redis-service" to "localhost" or "127.0.0.1"

    Basically this treats all services as running on a single host (which is the gluetun container), so all services are on the same Localhost, and can be accessed as such by each service's respective port. This worked, But puts ALL containers external traffic through the VPN. Good if that's what you want, But not if you're trying to just have a single container route like I am. This is also more intrusive because it requires Application level environment variable changes for the host names. You don't need to define an explicit network in this case. The docker-compose file here is :

    version: '3'
    services:
      redis-service:
        image: redis
        depends_on:
        - gluetun
        network_mode: "service:gluetun" # Uses Gluetun container's network
    # NOTE ports cannot be defined here when using network_mode, Must define ports in the gluetun service instead.
    
      default-queue-service:
        image: queue
        depends_on:
          - redis-service
        environment:
          CONTAINER_ROLE: Default-Queue-worker
          REDIS_HOST: localhost # This would be defined in your app env file
        network_mode: "service:gluetun" # Uses Gluetun container's network, External requests use VPN
    
      vpn-queue-service:
        image: queue
        depends_on:
          - redis-service
        environment:
          CONTAINER_ROLE: VPN-Queue-worker
          REDIS_HOST: localhost # This would be defined in your app env file
        network_mode: "service:gluetun" # Uses Gluetun container's network, External requests use VPN
    
      gluetun-service:
        image: qmcgaw/gluetun
        hostname: gluetun
        cap_add:
          - NET_ADMIN
        devices:
          - /dev/net/tun:/dev/net/tun
        ports:
          - 8888:8888/tcp # HTTP proxy
          - 8388:8388/tcp # Shadowsocks
          - 8388:8388/udp # Shadowsocks
          - "6379:6379" # Redis , Redis Container uses Gluetun container's network so ports must be defined here, Cannot be defined in service.
    #    volumes:
    #      - /yourpath:/gluetun
        environment:
          # See https://github.com/qdm12/gluetun-wiki/tree/main/setup#setup
          - VPN_SERVICE_PROVIDER=<PROVIDER>
          - VPN_TYPE=openvpn
          # OpenVPN:
          - OPENVPN_USER=<REDACTED>
          - OPENVPN_PASSWORD=<REDACTED>
          # Wireguard:
          # - WIREGUARD_PRIVATE_KEY=<REDACTED>
          # - WIREGUARD_ADDRESSES=<REDACTED>
          # Timezone for accurate log times
          - TZ=
          # Server list updater
          # See https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-the-vpn-servers-list
          - UPDATER_PERIOD=
          - SERVER_COUNTRIES=
          - SERVER_REGIONS=
    
    

    Here, Both queue workers use the VPN network for external requests (All services network mode is defined as the gluetun service Including the dependent service Redis), so there is no difference between them besides application level. The services can access redis via the Localhost as they are all on the same network (all containers are using the same ip address), differentiating services by ports alone.