A file-only repository containing OpenVPN + Privoxy s6-overlay service definitions, provider config scripts, and helper scripts. Consumed by downstream Docker images via git clone at build time — no Docker image is published.
| Project | Image |
|---|---|
| brycelarge/tuliprox-vpn | ghcr.io/brycelarge/tuliprox-vpn |
| brycelarge/m3u-manager | ghcr.io/brycelarge/m3u-manager |
In the downstream Dockerfile, during the final stage:
ARG OPENVPN_TOOLS_REF=main
RUN apk add --no-cache git && \
git clone --depth 1 --branch "${OPENVPN_TOOLS_REF}" \
https://github.com/brycelarge/openvpn-buildtools.git /tmp/openvpn-buildtools && \
cp -r /tmp/openvpn-buildtools/root/. / && \
cp -r /tmp/openvpn-buildtools/scripts/. /scripts/ && \
rm -rf /tmp/openvpn-buildtoolsPin to a specific tag for reproducible builds:
ARG OPENVPN_TOOLS_REF=v1.0.0- Provider config download scripts: PIA, NordVPN, Surfshark, IPVanish, VyprVPN, ProtonVPN, Custom
- s6-overlay service definitions:
init-openvpn-config— downloads provider configs, sets up credentials, addsLOCAL_NETWORKroutessvc-openvpn— runs the OpenVPN daemon with sensible defaultsinit-privoxy-config— configures Privoxy listen addresssvc-privoxy— runs Privoxy (only whenPRIVOXY_ENABLED=true)
- Helper scripts in
/scripts/:openvpn-config-validation.sh— resolves and validates the.ovpnconfig + auth fileopenvpn-config-clean.sh— normalises.ovpnfiles (line endings, deprecated options)vpntest.sh— quick VPN connectivity check (IP, DNS, routing)speedtest.sh— runsspeedtest-clior falls back toiperf3logging.sh— sharedlog/debug_loghelpersdos2unix.sh— CRLF → LF conversion
| Variable | Default | Description |
|---|---|---|
VPN_ENABLED |
false |
Set true to enable OpenVPN |
VPN_DIR |
/app/config/openvpn |
Where .ovpn configs and credentials are stored. Override in downstream images that use a different data path. |
OPENVPN_PROVIDER |
CUSTOM |
CUSTOM, PIA, SURFSHARK, IPVANISH, NORDVPN, VYPRVPN, PROTONVPN |
OPENVPN_CONFIG |
(first .ovpn found) |
Specific config filename (with or without .ovpn) |
OPENVPN_PROTOCOL |
udp |
udp or tcp |
OPENVPN_USERNAME |
VPN username — written to credentials file at startup | |
OPENVPN_PASSWORD |
VPN password — written to credentials file at startup | |
OPENVPN_OPTIONS |
Extra openvpn CLI flags appended verbatim | |
OPENVPN_FORCE_UPDATE |
false |
Set true to delete and re-download provider configs on next start |
NAME_SERVERS |
Comma-separated DNS servers — overwrites /etc/resolv.conf |
|
LOCAL_NETWORK |
||
PRIVOXY_ENABLED |
false |
Enable Privoxy HTTP proxy (requires VPN_ENABLED=true) |
PRIVOXY_PORT |
8118 |
Privoxy listen port |
PRIVOXY_STARTUP_DELAY_SECS |
30 |
Seconds to wait for tun0 before starting Privoxy |
VPN_LOG_DIR |
/app/config/logs/openvpn |
OpenVPN log directory |
PRIVOXY_LOG_DIR |
/app/config/logs/privoxy |
Privoxy log directory |
DEBUG |
false |
Verbose logging |
When VPN_ENABLED=true the container needs:
cap_add:
- NET_ADMIN
devices:
- /dev/net/tunThe final image must be based on brycelarge/alpine-baseimage (which provides s6-overlay) and must install openvpn + privoxy via apk (dynamically linked — only scripts/configs come from this repo).
ARG OPENVPN_TOOLS_REF=main
FROM brycelarge/alpine-baseimage:3.21
RUN apk add --no-cache git openvpn privoxy iproute2 iptables jq moreutils dos2unix && \
git clone --depth 1 --branch "${OPENVPN_TOOLS_REF}" \
https://github.com/brycelarge/openvpn-buildtools.git /tmp/openvpn-buildtools && \
cp -r /tmp/openvpn-buildtools/root/. / && \
cp -r /tmp/openvpn-buildtools/scripts/. /scripts/ && \
rm -rf /tmp/openvpn-buildtoolsRegister the services in your user bundle (root/etc/s6-overlay/s6-rc.d/user/contents.d/):
init-openvpn-config
svc-openvpn
init-privoxy-config
svc-privoxy
If your data path differs from /app/config/openvpn, set VPN_DIR in your environment:
environment:
- VPN_DIR=/data/config/openvpnMount your .ovpn file(s) into $VPN_DIR (default /app/config/openvpn):
volumes:
- ./data/config/openvpn:/app/config/openvpn
environment:
- VPN_ENABLED=true
- OPENVPN_PROVIDER=CUSTOM
- OPENVPN_CONFIG=my-server.ovpn
- OPENVPN_USERNAME=user
- OPENVPN_PASSWORD=passConfigs are downloaded automatically on first start into $VPN_DIR/<provider>/ and cached across restarts.
environment:
- VPN_ENABLED=true
- OPENVPN_PROVIDER=NORDVPN
- OPENVPN_CONFIG=us9196.nordvpn.com.udp
- OPENVPN_USERNAME=your_service_user
- OPENVPN_PASSWORD=your_service_pass
- LOCAL_NETWORK=192.168.1.0/24For NordVPN, service credentials (not your account password) are at: https://my.nordaccount.com/dashboard/nordvpn/manual-configuration/
- OPENVPN_FORCE_UPDATE=trueOr delete the cached directory and restart:
rm -rf ./data/config/openvpn/nordvpn
docker compose restartdocker exec <container> /etc/openvpn/surfshark/map.shWrites $VPN_DIR/surfshark_map.json. Then OPENVPN_CONFIG can be a friendly key like za_johannesburg.
When OpenVPN connects it takes over the default route. Without LOCAL_NETWORK, all traffic (including your app's port) goes through the tunnel and your host can no longer reach the container.
- LOCAL_NETWORK=192.168.1.0/24Multiple subnets:
- LOCAL_NETWORK=192.168.1.0/24,10.0.0.0/8An optional HTTP proxy that routes traffic through the VPN tunnel. Only starts when both VPN_ENABLED=true and PRIVOXY_ENABLED=true.
- PRIVOXY_ENABLED=true
- PRIVOXY_PORT=8118Expose the port:
ports:
- "8118:8118"All services log via s6-log with rotation (20 files × 1 MB). Log directories are created at runtime — mount your data volume to persist them.
| Service | Env var | Default path |
|---|---|---|
| OpenVPN | VPN_LOG_DIR |
/app/config/logs/openvpn |
| Privoxy | PRIVOXY_LOG_DIR |
/app/config/logs/privoxy |
docker exec <container> /scripts/vpntest.shChecks: tun0 interface, public IP, country/ASN, DNS resolution, default route, and optionally runs a speed test.
Changes to scripts or s6 service definitions here automatically flow into all downstream images on their next build — no per-project changes needed. When adding a new provider:
- Add
root/etc/openvpn/<provider>/update.sh - Handle the provider in
scripts/openvpn-config-validation.shandroot/etc/s6-overlay/s6-rc.d/init-openvpn-config/run