Lightweight Docker: OpenVPN + Privoxy + AdGuard DNS for ad/adult content blocking via VPN.
3.1K
Lightweight Docker container that connects to any OpenVPN server and exposes the tunnel as a plain HTTP proxy on port 3128 β no VPN client needed on the host, no root config, just point your app at the proxy.
docker run \
--cap-add=NET_ADMIN \
--device /dev/net/tun \
-v ./vpn-config:/vpn:ro \
-p 127.0.0.1:3128:3128 \
titidnh/openvpn_client_proxy:latest
curl --proxy http://127.0.0.1:3128 https://api.ipify.org
# β your VPN exit IP
| π VPN Kill Switch | iptables DROP by default β nothing leaks if the tunnel drops |
| π‘οΈ DNS Leak Protection | All DNS forced through local dnsmasq, upstream only via the tunnel |
| π Auto-Reconnect | Built-in supervisor restarts any failed service with exponential backoff |
| π HTTP Proxy | Privoxy on :3128 β works with any app that supports HTTP_PROXY |
| π Optional Proxy Auth | Set PROXY_USER + PROXY_PASS to require Basic Auth (nginx fronts Privoxy) |
| π§Ή DNS Filtering | Configurable upstream DNS β AdGuard, Cloudflare, Quad9, or any resolver |
| π Tailscale Exit Node | Optional β route your entire Tailscale network through the VPN |
| π³ Multi-arch | linux/amd64 and linux/arm64 |
| π¦ ~120 MB image | Alpine base + multi-stage build (Tailscale binaries only) |
./vpn-config/
βββ vpn.conf β your provider's .ovpn file, renamed
βββ vpn.auth β optional: username on line 1, password on line 2
Most VPN providers (Mullvad, ProtonVPN, NordVPNβ¦) let you export an .ovpn file β just rename it vpn.conf.
docker run \
--cap-add=NET_ADMIN \
--device /dev/net/tun \
--rm \
-v ./vpn-config:/vpn:ro \
-p 127.0.0.1:3128:3128 \
titidnh/openvpn_client_proxy:latest
# curl
curl --proxy http://127.0.0.1:3128 https://api.ipify.org
# environment variable (works for most CLI tools)
export HTTP_PROXY="http://127.0.0.1:3128"
export HTTPS_PROXY="http://127.0.0.1:3128"
# Python
proxies = {"http": "http://127.0.0.1:3128", "https": "http://127.0.0.1:3128"}
requests.get("https://api.ipify.org", proxies=proxies)
| Variable | Default | Description |
|---|---|---|
DNS_SERVER_1 | 94.140.14.14 | Primary upstream DNS (AdGuard Default β ads blocked, no adult filter) |
DNS_SERVER_2 | 94.140.15.15 | Secondary upstream DNS |
PROXY_USER | (empty) | Enable Basic Auth β set together with PROXY_PASS |
PROXY_PASS | (empty) | Proxy password (bcrypt-hashed at startup via htpasswd) |
ENABLE_TAILSCALE | false | Start Tailscale inside the container |
TAILSCALE_AUTHKEY | (empty) | Tailscale pre-auth key |
TAILSCALE_ADVERTISE_EXIT_NODE | false | Advertise as a Tailscale exit node |
TAILSCALE_HOSTNAME | openvpn-client-proxy | Hostname shown in Tailscale admin |
TAILSCALE_ACCEPT_ROUTES | false | Accept routes from the tailnet |
TAILSCALE_FLAGS | (empty) | Extra flags passed verbatim to tailscale up |
-e DNS_SERVER_1=1.1.1.1 -e DNS_SERVER_2=1.0.0.1 # Cloudflare β no filter
-e DNS_SERVER_1=9.9.9.9 -e DNS_SERVER_2=149.112.112.112 # Quad9 β malware/phishing
-e DNS_SERVER_1=94.140.14.15 -e DNS_SERVER_2=94.140.15.16 # AdGuard Family β ads + adult
-e DNS_SERVER_1=8.8.8.8 -e DNS_SERVER_2=8.8.4.4 # Google β no filter
services:
vpnproxy:
image: titidnh/openvpn_client_proxy:latest
container_name: vpn_proxy
restart: always
cap_add: [ NET_ADMIN ]
devices: [ "/dev/net/tun" ]
volumes:
- ./vpn-config:/vpn:ro
ports:
- "127.0.0.1:3128:3128"
services:
vpnproxy:
image: titidnh/openvpn_client_proxy:latest
container_name: vpn_proxy
restart: always
cap_add: [ NET_ADMIN ]
devices: [ "/dev/net/tun" ]
volumes:
- ./vpn-config:/vpn:ro
ports:
- "3128:3128" # safe to expose β auth required
environment:
PROXY_USER: "alice"
PROXY_PASS: "s3cr3t!"
curl --proxy http://alice:[email protected]:3128 https://api.ipify.org
services:
vpnproxy:
image: titidnh/openvpn_client_proxy:latest
restart: always
cap_add: [ NET_ADMIN ]
devices: [ "/dev/net/tun" ]
volumes:
- ./vpn-config:/vpn:ro
myapp:
image: myapp:latest
depends_on:
vpnproxy:
condition: service_healthy
environment:
HTTP_PROXY: "http://vpn_proxy:3128"
HTTPS_PROXY: "http://vpn_proxy:3128"
services:
vpnproxy:
image: titidnh/openvpn_client_proxy:latest
restart: always
cap_add: [ NET_ADMIN ]
devices: [ "/dev/net/tun" ]
sysctls:
net.ipv4.ip_forward: "1"
net.ipv6.conf.all.forwarding: "1"
volumes:
- ./vpn-config:/vpn:ro
- tailscale-state:/var/lib/tailscale
ports:
- "3128:3128"
environment:
PROXY_USER: "alice"
PROXY_PASS: "s3cr3t!"
ENABLE_TAILSCALE: "true"
TAILSCALE_AUTHKEY: "tskey-auth-xxxx"
TAILSCALE_ADVERTISE_EXIT_NODE: "true"
TAILSCALE_HOSTNAME: "my-vpn-exit-node"
volumes:
tailscale-state:
The proxy listens on 0.0.0.0:3128 with no authentication by default. Pick at least one mitigation:
| Option | How |
|---|---|
| Enable auth | Set PROXY_USER + PROXY_PASS β nginx enforces Basic Auth, bcrypt-hashed |
| Localhost only | -p 127.0.0.1:3128:3128 |
| Internal network | Don't publish the port β use a named Docker bridge between containers |
Basic Auth sends credentials in base64. For exposure beyond localhost, combine with auth + a trusted network boundary.
NET_ADMIN capability + /dev/net/tun devicetun module loaded (modprobe tun).ovpn config file from your VPN providerFull documentation, all configuration options, and troubleshooting guide on GitHub: github.com/titidnh/openvpn_client_proxyβ
Content type
Image
Digest
sha256:10751d640β¦
Size
60.5 MB
Last updated
3 months ago
docker pull titidnh/openvpn_client_proxy