Private Internet Access Client (OpenVPN+Iptables+DNS over TLS on Alpine Linux)

Lightweight VPN client to tunnel to private internet access servers

PIA Docker OpenVPN

Build Status Docker Build Status

GitHub last commit GitHub commit activity GitHub issues

Docker Pulls Docker Stars Docker Automated

Image size Image version

Donate PayPal

Image size RAM usage CPU usage
19.8MB 14MB to 80MB Low to Medium
Click to show base components

Features

  • Configure everything with environment variables

    • Destination region
    • Internet protocol
    • Level of encryption
    • PIA Username and password
    • DNS over TLS
    • Malicious DNS blocking
    • Internal firewall
    • Web HTTP proxy
    • Run openvpn without root

  • Connect other containers to it, see this

  • The iptables firewall allows traffic only with needed PIA servers (IP addresses, port, protocol) combinations

  • OpenVPN reconnects automatically on failure

  • Docker healthcheck pings the DNS 1.1.1.1 to verify the connection is up

  • Unbound DNS runs without root

  • OpenVPN can run without root but this disallows OpenVPN reconnecting, it can be set with NONROOT=yes

  • ARM compatible

  • Port forwarding

  • HTTP proxy for LAN devices

Setup

  1. Requirements

    • A Private Internet Access username and password - Sign up
    • Firewall requirements
      • Allow outbound TCP 853 to 1.1.1.1 to allow Unbound to resolve the PIA domain name at start. You can then block it once the container is started.
      • For UDP strong encryption, allow outbound UDP 1197
      • For UDP normal encryption, allow outbound UDP 1198
      • For TCP strong encryption, allow outbound TCP 501
      • For TCP normal encryption, allow outbound TCP 502

  2. Ensure /dev/net/tun is setup on your host with either:

    insmod /lib/modules/tun.ko
    # or...
    modprobe tun
    
  3. CLICK IF YOU HAVE AN ARM DEVICE

    • If you have a ARM 32 bit v6 architecture

      docker build -t qmcgaw/private-internet-access \
      --build-arg BASE_IMAGE=arm32v6/alpine \
      https://github.com/qdm12/private-internet-access-docker.git
      
    • If you have a ARM 32 bit v7 architecture

      docker build -t qmcgaw/private-internet-access \
      --build-arg BASE_IMAGE=arm32v7/alpine \
      https://github.com/qdm12/private-internet-access-docker.git
      
    • If you have a ARM 64 bit v8 architecture

      docker build -t qmcgaw/private-internet-access \
      --build-arg BASE_IMAGE=arm64v8/alpine \
      https://github.com/qdm12/private-internet-access-docker.git
      

  4. Launch the container with:

    docker run -d --init --name=pia --cap-add=NET_ADMIN --device=/dev/net/tun \
    -e REGION="CA Montreal" -e USER=js89ds7 -e PASSWORD=8fd9s239G \
    qmcgaw/private-internet-access
    

    or use docker-compose.yml with:

    docker-compose up -d
    

    Note that you can change all the environment variables.

    If you want to use the HTTP proxy, add -p 8888:8888/tcp so that it is accessible from LAN devices.

Testing

Check the PIA IP address matches your expectations

docker run --rm --network=container:pia alpine:3.10 wget -qO- https://ipinfo.io

Environment variables

Environment variable Default Description
REGION CA Montreal One of the PIA regions
PROTOCOL udp tcp or udp
ENCRYPTION strong normal or strong
USER Your PIA username
PASSWORD Your PIA password
NONROOT no Run OpenVPN without root, yes or no
DOT on on or off, to activate DNS over TLS to 1.1.1.1
BLOCK_MALICIOUS off on or off, blocks malicious hostnames and IPs
BLOCK_NSA off on or off, blocks NSA hostnames
UNBLOCK comma separated string (i.e. web.com,web2.ca) to unblock hostnames
FIREWALL on on or off, to switch the internal killswitch firewall (should be left on)
EXTRA_SUBNETS comma separated subnets allowed in the container firewall (i.e. 192.168.1.0/24,192.168.10.121,10.0.0.5/28)
PROXY on on or off, to switch the internal HTTP proxy
PROXY_LOG_LEVEL Critical Info, Warning, Error or Critical
PROXY_USER Username to use to connect to the HTTP proxy
PROXY_PASSWORD Passsword to use to connect to the HTTP proxy

Connect to it

There are various ways to achieve this, depending on your use case.

  • Connect other containers to PIA

    Add --network=container:pia when launching the container

  • Connect containers from another docker-compose.yml

    Add network_mode: "container:pia" to your docker-compose.yml

  • Connect containers in the same docker-compose.yml as PIA

    Add network_mode: "service:pia" to your docker-compose.yml (no need for depends_on)

  • Access ports of containers connected to PIA

    To access port 8000 of container xyz and 9000 of container abc connected to PIA, you will need a reverse proxy such as qmcgaw/caddy-scratch (you can build it for ARM, see its readme)

    1. Create the file Caddyfile

      touch Caddyfile
      chown 1000 Caddyfile
      # chown 1000 because caddy-scratch runs as user ID 1000 by default
      chmod 600 Caddyfile
      

      with this content:

      :8000 {
          proxy / xyz:8000
      }
      :9000 {
          proxy / abc:9000
      }
      

      You can of course make more complicated Caddyfile (such as proxying /xyz to xyz:8000 and /abc to abc:9000, just ask me!)

    2. Run Caddy with

      docker run -d -p 8000:8000/tcp -p 9000:9000/tcp \
      --link pia:xyz --link pia:abc \
      -v $(pwd)/Caddyfile:/Caddyfile:ro \
      qmcgaw/caddy-scratch
      

      WARNING: Make sure the Docker network in which Caddy runs is the same as the one of PIA. It can be the default bridge network.

    3. You can now access xyz:8000 at localhost:8000 and abc:9000 at localhost:9000

    For more containers, add more --link pia:xxx and modify the Caddyfile accordingly

    If you want to user a docker-compose.yml, you can use this example - make sure PIA is launched and connected first:

    version: '3'
    services:
      piaproxy:
        image: qmcgaw/caddy-scratch
        container_name: piaproxy
        ports:
          - 8000:8000/tcp
          - 9000:9000/tcp
        external_links:
          - pia:xyz
          - pia:abc
        volumes:
          - ./Caddyfile:/Caddyfile:ro
      abc:
        image: abc
        container_name: abc
        network_mode: "container:pia"
      xyz:
        image: xyz
        container_name: xyz
        network_mode: "container:pia"
    

  • Access ports of containers connected to PIA, all in the same docker-compose.yml

    To access port 8000 of container xyz and 9000 of container abc connected to PIA, you could use:

    version: '3'
    services:
      pia:
        image: qmcgaw/private-internet-access
        container_name: pia
        cap_add:
          - NET_ADMIN
        devices:
          - /dev/net/tun
        environment:
          - USER=js89ds7
          - PASSWORD=8fd9s239G
        ports:
          - 8000:8000/tcp
          - 9000:9000/tcp
      abc:
        image: abc
        container_name: abc
        network_mode: "service:pia"
      xyz:
        image: xyz
        container_name: xyz
        network_mode: "service:pia"
    

  • Access ports of containers connected to PIA, all in the same docker-compose.yml, using a reverse proxy

    To access port 8000 of container xyz and 9000 of container abc connected to PIA, you will need a reverse proxy such as qmcgaw/caddy-scratch (you can build it for ARM, see its readme)

    1. Create the file Caddyfile

      touch Caddyfile
      chown 1000 Caddyfile
      # chown 1000 because caddy-scratch runs as user ID 1000 by default
      chmod 600 Caddyfile
      

      with this content:

      :8000 {
          proxy / xyz:8000
      }
      :9000 {
          proxy / abc:9000
      }
      

      You can of course make more complicated Caddyfile (such as proxying /xyz to xyz:8000 and /abc to abc:9000, just ask me!)

    2. Use this example:

      version: '3'
      services:
        pia:
          image: qmcgaw/private-internet-access
          container_name: pia
          cap_add:
            - NET_ADMIN
          devices:
            - /dev/net/tun
          environment:
            - USER=js89ds7
            - PASSWORD=8fd9s239G
        piaproxy:
          image: qmcgaw/caddy-scratch
          container_name: piaproxy
          ports:
            - 8000:8000/tcp
            - 9000:9000/tcp
          external_links:
            - pia:xyz
            - pia:abc
          volumes:
            - ./Caddyfile:/Caddyfile:ro
        abc:
          image: abc
          container_name: abc
          network_mode: "service:pia"
        xyz:
          image: xyz
          container_name: xyz
          network_mode: "service:pia"
      

  • Connect to the PIA through an HTTP proxy (i.e. with Chrome, Kodi, etc.)

    1. Setup a HTTP proxy client, such as SwitchyOmega for Chrome

    2. Make sure the PIA container:

      • Has port 8888 published -p 8888:8888/tcp
      • Has your LAN in EXTRA_SUBNETS
    3. With your HTTP proxy client, connect to the Docker host (i.e. 192.168.1.10) on port 8888. You might need to enter your credentials if you set them with the environment variables PROXY_USER and PROXY_PASSWORD.

    4. If you set PROXY_LOG_LEVEL to Info, you can check the log output of tinyproxy with:

      docker exec -it pia cat /var/log/tinyproxy/tinyproxy.log
      

      PROXY_LOG_LEVEL defaults to Critical to avoid logging everything, for privacy purposes as well as to save storage.

Port forwarding

On a running PIA container, say pia, simply run:

docker exec -it pia /portforward.sh

And it will indicate you the port forwarded for your current public IP address.

Note that not all regions support port forwarding.

For the paranoids

  • You can review the code which essential consists in the Dockerfile and entrypoint.sh

  • Build the images yourself:

    docker build -t qmcgaw/private-internet-access https://github.com/qdm12/private-internet-access-docker.git
    
  • The download and unziping of PIA openvpn files is done at build for the ones not able to download the zip files

  • Checksums for PIA openvpn zip files are not used as these files change often (but HTTPS is used)

  • Use -e ENCRYPTION=strong -e BLOCK_MALICIOUS=on

  • DNS Leaks tests might not work because of this (TLDR: DNS server is a local caching intermediary)

TODOs

License

This repository is under an MIT license

Description
VPN client in a thin Docker container for multiple VPN providers, written in Go, and using OpenVPN or Wireguard, DNS over TLS, with a few proxy servers built-in.
Readme MIT 33 MiB
Languages
Go 99.4%
Dockerfile 0.6%