Initial release

This commit is contained in:
Maxim Kalistratov
2026-07-26 05:13:30 +04:00
commit 47a50832b3
11 changed files with 1064 additions and 0 deletions
+594
View File
@@ -0,0 +1,594 @@
# Egress Proxy
> [!WARNING]
> This project was created with the assistance of an AI agent.
>
> Although it has been tested in production, it is provided as-is. I do not accept responsibility for configuration errors, service interruptions, exposed credentials, open proxy abuse, or security vulnerabilities.
A Docker Compose wrapper around [tarampampam/3proxy-docker](https://github.com/tarampampam/3proxy-docker) designed to expose local HTTP and SOCKS proxy endpoints and route their traffic through an authenticated upstream HTTP proxy.
The stack preserves the dynamic configuration provided by the original image and adds optional deployment integrations for standalone Docker, Traefik, Nginx, HAProxy, and other TCP reverse proxies.
## Requirements
* Docker Engine
* Docker Compose v2
* An upstream HTTP proxy
* Upstream proxy hostname or IP address
* Upstream proxy port
* Upstream proxy username and password
* Optional external Docker network when integrating with Traefik, Nginx, or HAProxy
The upstream proxy must support the HTTP `CONNECT` method for HTTPS and SOCKS TCP forwarding.
## Features
* HTTP forward proxy on port `3128`
* SOCKS proxy on port `1080`
* Routes supported traffic through an authenticated upstream HTTP proxy
* No authentication required from local proxy clients
* Dynamic configuration through environment variables
* Preserves native `3proxy-docker` configuration options
* Does not replace the original image entrypoint
* Does not mount a complete custom `3proxy.cfg`
* Uses the image's native `EXTRA_CONFIG` mechanism
* Standalone deployment with directly published ports
* Traefik TCP router integration
* Nginx and HAProxy integration through a shared Docker network
* Optional PROXY protocol support with Traefik
* Configurable client IP allowlist
* Configurable DNS resolvers, connection limits, ports, and listener arguments
* Automatic restart through Docker restart policies
* Smoke-test suite for HTTP, HTTPS, and SOCKS forwarding
* Optional verification of the expected upstream exit IP
## Repository Structure
```text
.
├── .env.example
├── .gitattributes
├── .gitignore
├── README.md
├── compose.yaml
├── compose.standalone.yaml
├── compose.traefik.yaml
├── compose.external-network.yaml
├── compose.test.yaml
└── tests/
├── run.sh
└── smoke.sh
```
## How It Works
The base image generates the normal 3proxy configuration using its built-in environment-variable support.
This project adds parent routing rules through `EXTRA_CONFIG`.
The generated configuration is equivalent to:
```text
parentretries 3
auth iponly
allow * * * * HTTP,HTTPS
parent 1000 http "proxy.example.com" 8080 "username" "password"
allow * * * * CONNECT
parent 1000 connect "proxy.example.com" 8080 "username" "password"
deny *
```
The operation-specific access rules are important:
* `HTTP,HTTPS` handles requests received by the HTTP proxy listener.
* `CONNECT` handles TCP connections received by the SOCKS listener.
* `http` is used as the upstream parent type for the HTTP listener.
* `connect` is used to tunnel SOCKS TCP traffic through the upstream HTTP proxy.
* SOCKS UDP association and bind operations are not supported by an upstream HTTP proxy.
The stack uses:
```text
auth iponly
```
This does not require clients to provide a username or password. It enables 3proxy ACL and parent-routing processing.
Using:
```text
auth none
```
would disable the ACL chain and may cause requests to bypass the upstream proxy.
## Configuration
Copy the example environment file:
```bash
cp .env.example .env
```
At minimum, configure the upstream proxy:
```dotenv
UPSTREAM_HOST=proxy.example.com
UPSTREAM_PORT=8080
UPSTREAM_USERNAME=proxy-user
UPSTREAM_PASSWORD='proxy-password'
```
The `.env` file is ignored by Git and must not be committed.
### Credentials with Special Characters
Use single quotes for values containing `$`, `#`, spaces, or similar characters:
```dotenv
UPSTREAM_PASSWORD='my$p@ssword#123'
```
Avoid double quotes and newline characters inside upstream credentials because the values are inserted into quoted 3proxy configuration arguments.
## Available Environment Variables
### Image and Project
```dotenv
COMPOSE_PROJECT_NAME=egress-proxy
THREEPROXY_IMAGE=ghcr.io/tarampampam/3proxy:2
```
### Upstream Proxy
```dotenv
UPSTREAM_HOST=proxy.example.com
UPSTREAM_PORT=8080
UPSTREAM_USERNAME=proxy-user
UPSTREAM_PASSWORD='proxy-password'
```
### Local Proxy Listeners
```dotenv
PROXY_PORT=3128
SOCKS_PORT=1080
```
### DNS
```dotenv
PRIMARY_RESOLVER=1.1.1.1
SECONDARY_RESOLVER=8.8.8.8
DNS_CACHE_SIZE=65536
```
### Limits
```dotenv
MAX_CONNECTIONS=100
PARENT_RETRIES=3
NOFILE_SOFT=2048
NOFILE_HARD=4096
```
### Client Access
Allow all clients:
```dotenv
CLIENT_ALLOW=*
```
Allow a private network:
```dotenv
CLIENT_ALLOW=192.168.0.0/16
```
Allow several addresses or networks:
```dotenv
CLIENT_ALLOW=192.168.0.0/16,203.0.113.10
```
### Listener Arguments
Additional arguments can be appended to the generated listeners:
```dotenv
PROXY_EXTRA_ARGS=
SOCKS_EXTRA_ARGS=
```
These values preserve the customization options provided by the original `3proxy-docker` image.
## Validate the Configuration
Before starting the stack, validate the merged Compose configuration.
Standalone deployment:
```bash
docker compose \
-f compose.yaml \
-f compose.standalone.yaml \
config
```
Traefik deployment:
```bash
docker compose \
-f compose.yaml \
-f compose.traefik.yaml \
config
```
External network deployment:
```bash
docker compose \
-f compose.yaml \
-f compose.external-network.yaml \
config
```
Test deployment:
```bash
docker compose \
-f compose.yaml \
-f compose.test.yaml \
config
```
## Standalone Deployment
Use the standalone overlay when there is no existing reverse proxy.
By default, the proxy binds only to localhost:
```dotenv
BIND_ADDRESS=127.0.0.1
HTTP_PUBLIC_PORT=3128
SOCKS_PUBLIC_PORT=1080
```
Start the stack:
```bash
docker compose \
-f compose.yaml \
-f compose.standalone.yaml \
up -d
```
Test the HTTP proxy:
```bash
curl \
--proxy http://127.0.0.1:3128 \
https://api.ipify.org
```
Test the SOCKS proxy:
```bash
curl \
--proxy socks5h://127.0.0.1:1080 \
https://api.ipify.org
```
View logs:
```bash
docker compose \
-f compose.yaml \
-f compose.standalone.yaml \
logs -f proxy
```
Stop the stack:
```bash
docker compose \
-f compose.yaml \
-f compose.standalone.yaml \
down
```
### LAN Access
To expose the proxy on all server interfaces:
```dotenv
BIND_ADDRESS=0.0.0.0
```
Restrict clients when exposing the service outside localhost:
```dotenv
CLIENT_ALLOW=192.168.0.0/16
```
Do not expose an unauthenticated proxy to the public internet without an IP allowlist, firewall rules, VPN, private network, or equivalent access control.
## Traefik Deployment
3proxy must be exposed through Traefik TCP routers, not normal HTTP routers.
The HTTP proxy and SOCKS listener are separate TCP services and require dedicated Traefik entrypoints.
Add the following static configuration to the existing Traefik service:
```yaml
services:
traefik:
command:
- --entrypoints.proxy-http.address=:3128/tcp
- --entrypoints.proxy-socks.address=:1080/tcp
ports:
- "3128:3128/tcp"
- "1080:1080/tcp"
```
Create the shared network if it does not already exist:
```bash
docker network create traefik
```
Configure the integration:
```dotenv
TRAEFIK_NETWORK=traefik
TRAEFIK_ROUTER_PREFIX=egress-proxy
TRAEFIK_HTTP_ENTRYPOINT=proxy-http
TRAEFIK_SOCKS_ENTRYPOINT=proxy-socks
```
Start the stack:
```bash
docker compose \
-f compose.yaml \
-f compose.traefik.yaml \
up -d
```
The Traefik overlay:
* Creates one TCP router and service for the HTTP proxy.
* Creates one TCP router and service for SOCKS.
* Uses `HostSNI(*)` as a catch-all TCP routing rule.
* Sends PROXY protocol v1 to 3proxy.
* Enables `-H` on both 3proxy listeners.
* Preserves the original client IP.
* Does not publish ports directly from the 3proxy container.
Do not combine `compose.standalone.yaml` and `compose.traefik.yaml`.
In Traefik mode, Traefik should be the only service publishing ports `3128` and `1080`.
## Existing Nginx or HAProxy
Use the external-network overlay when an existing containerized TCP proxy should connect to 3proxy through Docker networking.
Create the external network:
```bash
docker network create edge
```
Configure it:
```dotenv
EDGE_NETWORK=edge
EDGE_SERVICE_ALIAS=egress-proxy
```
Start the stack:
```bash
docker compose \
-f compose.yaml \
-f compose.external-network.yaml \
up -d
```
Other containers attached to the same network can reach:
```text
egress-proxy:3128
egress-proxy:1080
```
### Nginx Example
Nginx must use the `stream` context rather than the regular `http` context:
```nginx
stream {
upstream egress_http_proxy {
server egress-proxy:3128;
}
upstream egress_socks_proxy {
server egress-proxy:1080;
}
server {
listen 3128;
proxy_pass egress_http_proxy;
}
server {
listen 1080;
proxy_pass egress_socks_proxy;
}
}
```
Without PROXY protocol, 3proxy sees the Nginx container address instead of the original client address.
In that case, client access restrictions should be applied in Nginx, or PROXY protocol should be configured on both sides.
## Smoke Tests
The test suite starts the proxy on an internal Docker network and verifies:
1. An HTTP request through the HTTP proxy listener.
2. An HTTPS request through the HTTP proxy listener.
3. An HTTPS request through the SOCKS listener.
4. That the requests return a public exit IP.
5. Optionally, that all listeners use the expected upstream exit IP.
6. Optionally, that proxied traffic does not use the server's direct public IP.
Run the tests:
```bash
sh tests/run.sh
```
The command exits with:
* `0` when all checks pass.
* A non-zero code when a request or assertion fails.
The test stack is removed automatically after completion.
### Fixed Exit IP
When the upstream proxy has a fixed public IP, configure:
```dotenv
EXPECTED_EXIT_IP=203.0.113.20
```
This is the strongest validation method.
All HTTP, HTTPS, and SOCKS requests must return that exact IP.
### Rotating Exit IPs
When the upstream proxy uses rotating IP addresses:
```dotenv
EXPECTED_EXIT_IP=
ASSERT_SAME_PROXY_EXIT=false
ASSERT_DIFFERENT_FROM_DIRECT=true
```
The test verifies that each request succeeds and does not use the server's direct public IP.
### No Direct Internet Access
When the test container cannot access the internet without the proxy:
```dotenv
ASSERT_DIFFERENT_FROM_DIRECT=false
```
Set `EXPECTED_EXIT_IP` when possible.
## Applying Configuration Changes
After modifying `.env`, recreate the container.
Standalone:
```bash
docker compose \
-f compose.yaml \
-f compose.standalone.yaml \
up -d --force-recreate
```
Traefik:
```bash
docker compose \
-f compose.yaml \
-f compose.traefik.yaml \
up -d --force-recreate
```
External network:
```bash
docker compose \
-f compose.yaml \
-f compose.external-network.yaml \
up -d --force-recreate
```
## Diagnostics
View logs:
```bash
docker compose logs -f proxy
```
View service status:
```bash
docker compose ps
```
Inspect the final merged configuration:
```bash
docker compose \
-f compose.yaml \
-f compose.standalone.yaml \
config
```
Inspect the generated 3proxy configuration:
```bash
docker compose cp \
proxy:/etc/3proxy/3proxy.cfg \
./generated-3proxy.cfg
```
The generated configuration contains upstream credentials. Delete the copied file after debugging:
```bash
rm -f generated-3proxy.cfg
```
## Security Considerations
* Local clients are not required to authenticate.
* Restrict access using `CLIENT_ALLOW`, firewall rules, a VPN, or a private network.
* Do not expose an unrestricted proxy to the public internet.
* Upstream credentials are stored in `.env`.
* Docker administrators can inspect container environment variables.
* Never commit `.env`.
* Rotate credentials accidentally committed, logged, or shared publicly.
* Review generated Compose and 3proxy configuration before deployment.
* Pin the container image to a specific version when reproducible deployments are required.
* Keep Docker, Docker Compose, Traefik, Nginx, and the base image updated.
* Treat access to the Docker socket as administrative access to the host.
## Limitations
* The upstream must be an HTTP proxy.
* SOCKS UDP forwarding is not supported.
* SOCKS bind operations are not supported.
* Client authentication is not enabled.
* Reverse proxies must support raw TCP forwarding.
* Normal hostname-based HTTP routing is not applicable to SOCKS traffic.
* Hostname-based routing is generally not useful for a forward proxy because destination hostnames belong to outbound requests rather than the proxy service itself.
## License
Use, modify, and distribute this project under the terms of the repository license.