Skip to content

Latest commit

Β 

History

77 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

V2ProDock

V2ProDock logo

Docker ready Proxy protocols VPN gateway Fail-closed egress

A Dockerized V2Ray/Xray proxy client that manages the entire proxy lifecycle β€” from subscription parsing to automatic failover. Feed it a subscription URL, and it handles the rest: parses protocols, health-checks servers, and provides stable SOCKS5 + HTTP proxies for your other apps and containers.

Caution

Disclaimer: V2ProDock operates no proxy servers and provides no bandwidth of its own. You supply the subscription URL(s) β€” self-hosted or public β€” and the app routes your traffic through those third-party servers as a free alternative to paid proxies. Public configs are run by strangers, so treat exit nodes as untrusted: prefer TLS end-to-end, and expect no uptime, speed, or anonymity guarantees.

Failover changes your exit IP whenever the active upstream switches, and the VPN gateway is experimental (alpha) β€” do not use it in production or for anything sensitive to IP changes (IP-locked accounts, banking, anti-fraud systems). You are responsible for complying with your ISP's terms and the terms of every service you access through this project.

How it fits together

flowchart TB
    subgraph LAN["Your Local Network"]
        TV["Smart TVs / IoT<br/>IKEv2 / L2TP-IPsec"]
        SAT["Satellite Receivers<br/>PPTP + MPPE-128"]
    end
    subgraph GW["v2prodock-vpn Gateway"]
        DAEMONS["charon (IKEv2) Β· xl2tpd (L2TP) Β· pptpd (PPTP)"]
        IFACES["ppp / XFRM client interfaces"]
        FW{{"iptables fail-closed<br/>direct egress DROPPED"}}
        TUN(["tun0 β€” hev-socks5-tunnel"])
        DAEMONS --> IFACES --> FW --> TUN
    end
    subgraph CORE["v2prodock Core Proxy Engine"]
        XRAY[("Live Xray instances<br/>VLESS / VMess / Trojan / Shadowsocks")]
        API["auto-healthcheck + latency sort<br/>GET /proxies"]
    end
    NET["The Internet"]
    TV -- "UDP 500 / 4500 / 1701" --> DAEMONS
    SAT -- "TCP 1723 + GRE (proto 47)" --> DAEMONS
    TUN -- "SOCKS5, pinned to fastest alive" --> XRAY
    XRAY --> NET
    XRAY -.-> API
    style TUN fill:#2ea043,color:#fff
    style FW fill:#da3633,color:#fff
Loading

Pushed DNS is resolved through the same tunnel β€” clients cannot leak around the working proxy.

Features

  • Subscription-based β€” paste a v2ray subscription URL, it parses vless/vmess/trojan/shadowsocks configs automatically
  • Multi-instance β€” run N independent xray processes, each with its own proxy pair and failover
  • Auto-failover β€” health-checks every 60s, switches to the next working server on failure
  • Auto-refresh β€” re-fetches subscription every 120s for updated server lists
  • HTTP API β€” query live proxies sorted by latency at GET /proxies
  • Dynamic ports β€” all ports are auto-assigned, no hardcoded ranges
  • Docker bridge β€” other containers connect through the Docker network without port mapping
  • Multi-platform β€” auto-detects OS/arch and downloads the right xray-core binary
  • Low-end friendly β€” tunable memory limits, connection caps, and GC tuning for constrained devices

Quick Start

curl -fsSL https://raw.githubusercontent.com/411A/V2ProDock/main/install.sh | bash

Or clone manually:

git clone https://github.com/411A/V2ProDock.git && cd V2ProDock
[ -f .env ] || cp .env.example .env
# Edit .env with your subscription URL(s)
sudo bash install.sh

Multi-Instance Setup

Run multiple independent proxy instances in a single container:

# .env
SUBSCRIPTION_URLS=https://sub1.example,https://sub2.example,https://sub3.example
PROXY_INSTANCES=3

Port layout: all SOCKS5 ports come first, then all HTTP ports. Instance i of N gets SOCKS5=PORT_BASE+i and HTTP=PORT_BASE+N+i (e.g. with N=3: SOCKS 27019-27021, HTTP 27022-27024). Query them via the API:

# Returns alive proxies sorted by lowest latency
curl http://localhost:27018/proxies
[
  {"index":1, "socks5":"0.0.0.0:27020", "http":"0.0.0.0:27023", "status":"ok", "latency_ms":85, "name":"server-1"},
  {"index":0, "socks5":"0.0.0.0:27019", "http":"0.0.0.0:27022", "status":"ok", "latency_ms":120, "name":"server-2"}
]

API Endpoints

Endpoint Method Description
/proxies GET Alive proxies sorted by latency (lowest first)
/all GET All instances including down ones
/health GET {"status":"ok","instances":3,"alive":2,"starting":1}
/vpn GET VPN pinning proof: upstream SOCKS + egress IP + verified
/refresh POST Force subscription re-fetch

Usage

From Python (multi-instance)

import requests

proxies = requests.get("http://localhost:27018/proxies").json()
proxy = proxies[0]  # Fastest proxy

r = requests.get("https://api.ipify.org", proxies={
    "http": f"http://{proxy['http']}",
    "https": f"socks5://{proxy['socks5']}",
})
print(r.text)

From other Docker containers

services:
  your-app:
    image: your-app
    environment:
      - HTTP_PROXY=http://v2prodock:27020
      - HTTPS_PROXY=socks5://v2prodock:27019
      - NO_PROXY=localhost,127.0.0.1,192.168.1.0/24
    networks:
      - proxy-net

networks:
  proxy-net:
    external: true
    name: v2prodock_v2prodock-proxy-net

VPN for legacy devices

TVs, IoT, consoles connect to the v2prodock-vpn sidecar. Its traffic is forced via tun0 into the fastest alive Xray SOCKS β€” fail-closed, never direct. Proofs land in the logs and in GET /vpn:

# .env
VPN_ENABLED=1
VPN_DOMAIN=192.168.1.10   # or public hostname/IP
VPN_USER=vpnuser
VPN_PASSWORD=change-me-8-chars-min
VPN_IPSEC_PSK=change-me-too-8-chars-min
curl http://localhost:27018/vpn
docker logs -f v2prodock-vpn   # look for: VPN egress VERIFIED via <name>: <ip>

Device quick setup

Device Protocol Settings Notes
MediaStar / StarSat / Ali-chipset receivers PPTP Server = your VPN_DOMAIN IP; user/pass from .env; encryption (ΩΎΩ†Ω‡Ψ§Ω†Ψ³Ψ§Ψ²ΫŒ) ON Set VPN_ENABLE_PPTP=1. Bridge mode works; switch to host networking only if reconnects stall.
Smart TVs, legacy routers L2TP/IPsec with pre-shared key Server = VPN_DOMAIN; IPsec PSK = VPN_IPSEC_PSK; user/pass from .env No files to import.
Apple TV / iPhone / Mac IKEv2 Install config/vpn/apple.mobileconfig Zero-config, certificate pinned.

Receiver connection checklist

  1. Set VPN_ENABLE_PPTP=1 in .env and docker compose up -d.
  2. On the box, server = the exact VPN_DOMAIN value (e.g. your VM's LAN IP).
  3. Encryption (ΩΎΩ†Ω‡Ψ§Ω†Ψ³Ψ§Ψ²ΫŒ) MUST be ON β€” the server strictly enforces MPPE-128.
  4. Reboot the box once after changing VPN settings; wait 30s+ before redialing.
  5. If redials still stall, switch to host networking (command below).

Linux host + PPTP reconnects stalling? Bridge mode is proven and stays the default β€” but if redials stall behind Docker NAT, host networking removes NAT from the GRE path entirely (recommended fallback, not a requirement): docker compose -f docker-compose.yml -f docker-compose.host.yml up -d --build

L2TP/IPsec β€” file-less login (recommended for legacy)

Type L2TP/IPsec with pre-shared key, server = VPN_DOMAIN, then enter the IPsec PSK (VPN_IPSEC_PSK) + PPP username/password. No files to import β€” works on old Windows/Android/routers natively.

Bare L2TP β€” old stock firmware only (opt-in, insecure)

Some old routers (e.g. stock Asus with bare-L2TP client, no IPsec option) cannot do IPsec at all. Set VPN_ALLOW_PLAIN_L2TP=1 to accept them: server = VPN_DOMAIN, no PSK, just PPP username/password. Everything β€” login and all traffic β€” crosses the internet in cleartext. Default 0 refuses bare L2TP at packet level. Use a unique strong password and only for devices that cannot do IPsec.

PPTP β€” ancient LAN devices only (opt-in, broken crypto)

Some devices speak nothing but PPTP β€” e.g. MediaStar / Ali-chipset satellite receivers whose VPN menu is just User + Password + an encryption toggle (ΩΎΩ†Ω‡Ψ§Ω†Ψ³Ψ§Ψ²ΫŒ = MPPE). Symptom: the box sends TCP SYN to port 1723 and times out because nothing listens there. Set VPN_ENABLE_PPTP=1 to serve PPTP on TCP 1723 (+ GRE protocol 47) with the same username/password as IKEv2/L2TP β€” enable the box's encryption option (MPPE-128 is mandatory server-side, no opt-out).

PPTP/MPPE is cryptographically broken β€” LAN-only, never expose TCP 1723 to the internet. The PPTP leg stays inside your trusted LAN; internet egress still goes via the working Xray proxy (fail-closed, same tun0 machinery, same VERIFIED proofs). How to tell it's PPTP: packet capture shows IP <box>.x > <server>.1723: Flags [S] (L2TP would be UDP 1701).

GRE note: GRE (IP proto 47) is not TCP/UDP, so Docker ports: cannot publish it. On a Linux host (e.g. Ubuntu 24.04 VM) the kernel helper nf_conntrack_pptp (loaded automatically by install.sh) forwards GRE to the container alongside the TCP 1723 DNAT. If PPTP control connects but data stalls, your kernel/cloud lacks GRE passthrough β€” run the vpn service with network_mode: host (Linux only) as the fallback. Diagnostics: docker exec v2prodock-vpn cat /var/log/ppp-pptp.log.

PPTP reconnects stall after a disconnect (bridge NAT)

Symptom: first connect works, then after an (abrupt/manual) disconnect the box redials into silence (LCP: timeout sending Config-Requests). Prime suspect is a stale GRE Call-ID mapping in the host's conntrack table: same Call ID reused while the old entry still lives, so return GRE goes to the dead session. The gateway already fights this three ways β€” dead sessions are reaped after ~30s (lcp-echo-interval 10, failure 3), every session teardown is logged ([pptp-ip-down] lines in /var/log/ppp-pptp.log), and teardown best-effort flushes the peer's GRE entries. Two honest limits: a container-side flush cannot reach the host NAT table in bridge mode (it is a no-op there, effective only under host networking), and no idle timeout is set on purpose β€” it would hang up your legitimately-idle receiver.

Fix ladder:

  1. Wait 30s+ before redialing (lets zombies die and conntrack entries age out), and reboot the box once after changing VPN settings β€” consumer firmware often keeps its own stale call state.
  2. If it recurs, switch the gateway to host networking (no NAT, no helper, no stale mappings involved at all): docker compose -f docker-compose.yml -f docker-compose.host.yml up -d --build
  3. Diagnose with: on the VM host sudo conntrack -L -p gre (stale entries for your box's IP during a failed redial confirm it) and in the container docker exec v2prodock-vpn tail -50 /var/log/ppp-pptp.log (a redial that never logs [pptp-ip-up] died before PPP β€” network/conntrack, not auth).

IKEv2 β€” needs one CA import

Server = VPN_DOMAIN (UDP 500/4500), EAP-MSCHAPv2 with the same username/password, after trusting config/vpn/ca.crt. Apple devices can use config/vpn/apple.mobileconfig instead. Connect using exactly VPN_DOMAIN (or a name/IP listed in VPN_EXTRA_SANS) β€” anything else fails server-identity validation.

Privileges & troubleshooting

The gateway needs NET_ADMIN + MKNOD + SYS_ADMIN, /dev/net/tun, and UDP 500/4500/1701 (+ TCP 1723 when PPTP is on, see compose) β€” least privilege verified, no --privileged. The host kernel needs PPP support (modprobe ppp_generic, plus ppp_mppe for PPTP and nf_conntrack_pptp for GRE passthrough β€” install.sh loads all of these). PPP diagnostics: docker exec v2prodock-vpn cat /var/log/ppp.log (L2TP) / /var/log/ppp-pptp.log (PPTP). Single client stalled while others work: toggle VPN off/on on that device (child-SA desync is DPD-blind; the server also recycles DATA SAs every 30 min regardless). Client-side kill switch (essential): our gateway is fail-closed, but a device whose own VPN SA dies will happily send traffic direct. On Android enable β€œBlock connections without VPN”, on Windows bind sensitive apps with firewall rules to the VPN interface, on iOS use OnDemand mode. Without this, no VPN provider can promise no-leak on the device itself.

VM / VPS firewall & LAN clients

Docker publishes the ports, but a default-deny host firewall still blocks them first. install.sh opens them automatically on ufw/firewalld when VPN_ENABLED=1; otherwise do it by hand:

sudo ufw allow 500,4500,1701/udp
# or: sudo firewall-cmd --permanent --add-port={500,4500,1701}/udp && sudo firewall-cmd --reload
# PPTP only (LAN-only!): sudo ufw allow from 192.168.0.0/16 to any port 1723 proto tcp

Cloud VMs need the same three UDP ports in the provider's security group. PPTP needs TCP 1723 restricted to your LAN in the host firewall β€” never open it to 0.0.0.0/0 (PPTP crypto is broken); GRE (proto 47) must also pass where the VM firewall filters by protocol. IP protocol ESP (50) is not required anywhere β€” all IPsec is forced through UDP/4500 encapsulation, which is also what makes same-LAN clients work (raw ESP cannot cross Docker's port NAT into the container).

Example: project runs in a VM at 192.168.1.100, legacy box on the same LAN connects L2TP to server 192.168.1.100 with your user/pass (+ PSK for L2TP/IPsec, none for opt-in bare L2TP). Set VPN_DOMAIN=192.168.1.100 so the server identity matches what clients dial. No host sysctls or forwarding setup needed β€” the container handles its own networking.

WSL2 note: stock WSL2 is NAT mode (172.x private IP), so physical LAN devices cannot reach services inside a WSL2 distro directly. This project runs on Docker Desktop (ports published on the Windows host's own interfaces, LAN-reachable), which we verified end-to-end from inside WSL2 (IKE handshake + L2TP control + generic UDP all answer). If you instead run dockerd inside WSL2 itself, LAN devices need netsh interface portproxy relays for UDP 500/4500/1701 from the Windows host into WSL2.

Commands

sudo bash install.sh           # Install & start
sudo bash install.sh start     # Start
sudo bash install.sh stop      # Stop
sudo bash install.sh status    # Show status
sudo bash install.sh logs      # Follow logs
sudo bash install.sh uninstall # Remove everything

How It Works

  1. Fetches subscription URL(s) and parses vless/vmess/trojan/shadowsocks links
  2. Converts each to an xray-core JSON outbound config
  3. Distributes configs across N instances (round-robin)
  4. Each instance: starts xray, tests configs, keeps the first working one
  5. Health checks run every 60s per instance β€” on failure, switches to next config
  6. API returns alive proxies sorted by latency β€” dead ones excluded
  7. Subscriptions re-fetched every 120s for updated server lists

Supported Protocols

Protocol Transport
VLESS TCP, WebSocket, gRPC, Reality
VMess TCP, WebSocket, gRPC
Trojan TCP, WebSocket
Shadowsocks TCP

Configuration

Environment variables (set in .env or via docker-compose):

Variable Default Description
SUBSCRIPTION_URL β€” Single v2ray subscription URL (see V2RayDAR to self-host one)
SUBSCRIPTION_URLS β€” Comma-separated URLs (one per instance, overrides SUBSCRIPTION_URL)
PROXY_INSTANCES 1 Number of xray instances to run
PORT_BASE 27019 Base port: N SOCKS5 ports, then N HTTP ports (SOCKS=base+i, HTTP=base+N+i)
API_PORT 27018 Port for the HTTP API
HEALTH_CHECK_URL http://api.ipify.org URL used to test proxy connectivity
XRAY_DIR /root/xray Path to xray binary directory
GOGC 100 Go GC target percentage (lower = more frequent GC, less memory)
GOMEMLIMIT 128MiB Go soft memory limit (prevents OOM by triggering aggressive GC)
MAX_CONNS 128 Max concurrent HTTP CONNECT relay connections
VPN_ENABLED 0 1 = serve VPN via working proxy
VPN_DOMAIN vpn.local Hostname/IP clients connect to (server cert SAN)
VPN_SUBNET 10.10.10.0/24 IKEv2 client pool (forwarded to tun0 only)
VPN_L2TP_NET 10.10.11.0/24 L2TP client pool (forwarded to tun0 only)
VPN_ENABLE_PPTP 0 1 = serve PPTP on TCP 1723+GRE (ancient LAN devices only, MPPE-128 mandatory)
VPN_PPTP_NET 10.10.12.0/24 PPTP client pool (forwarded to tun0 only)
VPN_DNS 1.1.1.1,8.8.8.8 DNS pushed to clients (also via proxy)
VPN_USER / VPN_PASSWORD β€” EAP-MSCHAPv2 + PPP creds (or VPN_USERS=u1:p1,u2:p2)
VPN_IPSEC_PSK β€” IPsec pre-shared key for L2TP clients

Subscription URL

You need a v2ray subscription URL to get started. V2RayDAR fetches configs from public repos and serves them as a single subscription endpoint β€” run it on your server and point to:

http://192.168.x.x:27141/subscription

Docker note: Inside a container, 127.0.0.1 refers to the container itself, not your host. Use host.docker.internal (Docker 20.10+) or your host's LAN IP instead. The compose file already includes extra_hosts for host.docker.internal support on Linux.

WSL2 note: If V2RayDAR runs on Windows and V2ProDock runs in WSL2/Docker, host.docker.internal won't reach the Windows host. The install script auto-detects WSL2 and replaces it with the correct Windows IP. To fix manually:

WIN_HOST=$(ip route show default | awk '/default/ {print $3}')
sed -i "s|host.docker.internal|$WIN_HOST|g" .env
bash install.sh restart

You can also add more public subscription URLs directly via SUBSCRIPTION_URLS in .env β€” comma-separated, one per instance.

Tuning for Low-End Devices

For devices with limited RAM (256MB-512MB):

# .env β€” conservative defaults that won't OOM
GOGC=100
GOMEMLIMIT=128MiB
MAX_CONNS=64
PROXY_INSTANCES=1
# .env β€” aggressive for VPS with 1GB+ RAM
GOGC=50
GOMEMLIMIT=256MiB
MAX_CONNS=256
PROXY_INSTANCES=3

License

MIT

About

🌐 Turn any V2Ray subscription into self-healing SOCKS5/HTTP proxies & a hardware VPN gateway for Smart TVs, receivers, and Docker apps.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages