13 KiB
Executable file
go-proxy
A simple auto docker reverse proxy for home use. Written in Go
In the examples domain x.y.z
is used, replace them with your domain
Table of content
- go-proxy
Key Points
-
Fast (See benchmarks)
-
Auto certificate obtaining and renewal (See Config File and Supported DNS Challenge Providers)
-
Auto detect reverse proxies from docker
-
Custom proxy entries with
config.yml
and additional provider files -
Subdomain matching + Path matching (domain name doesn't matter)
-
HTTP(s) proxy + TCP/UDP Proxy
-
HTTP(s) round robin load balance support (same subdomain and path across different hosts)
-
Auto hot-reload on container
start
/die
/stop
or config file changes -
Simple panel to see all reverse proxies and health available on port panel_port_http (http) and port panel_port_https (https)
-
Config editor to edit config and provider files with validation
Validate and save file with Ctrl+S
How to use
-
Download and extract the latest release (or clone the repository if you want to try out experimental features)
-
Copy
config.example.yml
toconfig/config.yml
and modify the content to fit your needs -
(Optional) write your own
config/providers.yml
fromproviders.example.yml
Binary
-
(Optional) enabled HTTPS
-
Use autocert feature by completing
autocert
inconfig.yml
-
Use existing certificate
Prepare your wildcard (
*.y.z
) SSL cert incerts/
- cert / chain / fullchain:
./certs/cert.crt
- private key:
./certs/priv.key
- cert / chain / fullchain:
-
-
run the binary
bin/go-proxy
-
enjoy
Docker
-
Copy content from compose.example.yml and create your own
compose.yml
-
Add networks to make sure it is in the same network with other containers, or make sure
proxy.<alias>.host
is reachable -
(Optional) enable HTTPS
-
Use autocert feature
-
mount
./certs
to/app/certs
go-proxy: ... volumes: - ./certs:/app/certs
-
complete
autocert
inconfig.yml
-
-
Use existing certificate
Mount your wildcard (
*.y.z
) SSL cert to enable https.- cert / chain / fullchain ->
/app/certs/cert.crt
- private key ->
/app/certs/priv.key
- cert / chain / fullchain ->
-
-
Start
go-proxy
withdocker compose up -d
ormake up
. -
(Optional) If you are using ufw with vpn that drop all inbound traffic except vpn, run below to allow docker containers to connect to
go-proxy
In case the network of your container is in subnet
172.16.0.0/16
(bridge), and vpn network is under100.64.0.0/10
(i.e. tailscale)sudo ufw allow from 172.16.0.0/16 to 100.64.0.0/10
You can also list CIDRs of all docker bridge networks by:
docker network inspect $(docker network ls | awk '$3 == "bridge" { print $1}') | jq -r '.[] | .Name + " " + .IPAM.Config[0].Subnet' -
-
start your docker app, and visit <container_name>.y.z
-
check the logs with
docker compose logs
ormake logs
to see if there is any error, check panel at [panel port] for active proxies
Use JSON Schema in VSCode
Modify .vscode/settings.json
to fit your needs
{
"yaml.schemas": {
"https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [
"config.example.yml",
"config.yml"
],
"https://github.com/yusing/go-proxy/raw/main/schema/providers.schema.json": [
"providers.example.yml",
"*.providers.yml",
]
}
}
Configuration
With container name, most of the time no label needs to be added.
Labels (docker)
-
proxy.aliases
: comma separated aliases for subdomain matching- default:
container_name
- default:
-
proxy.*.<field>
: wildcard label for all aliases
Below labels has a proxy.<alias>
prefix (i.e. proxy.nginx.scheme: http
)
-
scheme
: proxy protocol- default:
http
- allowed:
http
,https
,tcp
,udp
- default:
-
host
: proxy host- default:
container_name
- default:
-
port
: proxy port- default: first expose port (declared in
Dockerfile
ordocker-compose.yml
) http(s)
: number in range og0 - 65535
tcp/udp
:[<listeningPort>:]<targetPort>
listeningPort
: number, when it is omitted (not suggested), a free port starting from 20000 will be used.targetPort
: number, or predefined names (see constants.go:14)
- default: first expose port (declared in
-
no_tls_verify
: whether skip tls verify when scheme is https- default:
false
- default:
-
path
: proxy path (http(s) proxy only)- default: empty
-
path_mode
: mode for path handling- default: empty
- allowed: empty,
forward
,sub
empty
: remove path prefix from URL when proxying- apps.y.z/webdav -> webdav:80
- apps.y.z./webdav/path/to/file -> webdav:80/path/to/file
forward
: path remain unchanged- apps.y.z/webdav -> webdav:80/webdav
- apps.y.z./webdav/path/to/file -> webdav:80/webdav/path/to/file
sub
: (experimental) remove path prefix from URL and also append path to HTML link attributes (src
,href
andaction
) and Javascriptfetch(url)
by response body substitution e.g. apps.y.z/app1 -> webdav:80,href="/app1/path/to/file"
->href="/path/to/file"
-
load_balance
: enable load balance (docker only)- allowed:
1
,true
- allowed:
Environment variables
GOPROXY_DEBUG
: set to1
ortrue
to enable debug behaviors (i.e. output, etc.)GOPROXY_REDIRECT_HTTP
: set to0
orfalse
to disable http to https redirect (only when certs are located)
Config File
See config.example.yml for more
Fields
-
autocert
: autocert configurationemail
: ACME Emaildomains
: a list of domains for cert registrationprovider
: DNS Challenge provider, see Supported DNS Challenge Providersoptions
: provider specific options
-
providers
: reverse proxy providers configurationkind
: provider kind (string), see Provider Kindsvalue
: provider specific value
Provider Kinds
-
docker
: load reverse proxies from dockervalues:
FROM_ENV
: value from environment- full url to docker host (i.e.
tcp://host:2375
)
-
file
: load reverse proxies from provider filevalue: relative path of file to
config/
Provider File
Fields are same as docker labels starting from scheme
See providers.example.yml for examples
Supported DNS Challenge Providers
-
Cloudflare
auth_token
: your zone API token
Follow this guide to create a new token with
Zone.DNS
read and edit permissions
Examples
Single port configuration example
# (default) https://<container_name>.y.z
whoami:
image: traefik/whoami
container_name: whoami # => whoami.y.z
# enable both subdomain and path matching:
whoami:
image: traefik/whoami
container_name: whoami
labels:
- proxy.aliases=whoami,apps
- proxy.apps.path=/whoami
# 1. visit https://whoami.y.z
# 2. visit https://apps.y.z/whoami
Multiple ports configuration example
minio:
image: quay.io/minio/minio
container_name: minio
...
labels:
- proxy.aliases=minio,minio-console
- proxy.minio.port=9000
- proxy.minio-console.port=9001
# visit https://minio.y.z to access minio
# visit https://minio-console.y.z/whoami to access minio console
TCP/UDP configuration example
# In the app
app-db:
image: postgres:15
container_name: app-db
...
labels:
# Optional (postgres is in the known image map)
- proxy.app-db.scheme=tcp
# Optional (first free port will be used for listening port)
- proxy.app-db.port=20000:postgres
# In go-proxy
go-proxy:
...
ports:
- 80:80
...
- 20000:20000/tcp
# or 20000-20010:20000-20010/tcp to declare large range at once
# access app-db via <*>.y.z:20000
Load balancing Configuration Example
nginx:
...
deploy:
mode: replicated
replicas: 3
labels:
- proxy.nginx.load_balance=1 # allowed: [1, true]
Troubleshooting
Q: How to fix when it shows "no matching route for subdomain <subdomain>"?
A: Make sure the container is running, and <subdomain> matches any container name / alias
Benchmarks
Benchmarked with wrk
connecting traefik/whoami
's /bench
endpoint
Remote benchmark (client running wrk and go-proxy
server are different devices)
-
Direct connection
root@yusing-pc:~# wrk -t 10 -c 200 -d 10s -H "Host: bench.6uo.me" --latency http://10.0.100.3:8003/bench Running 10s test @ http://10.0.100.3:8003/bench 10 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 94.75ms 199.92ms 1.68s 91.27% Req/Sec 4.24k 1.79k 18.79k 72.13% Latency Distribution 50% 1.14ms 75% 120.23ms 90% 245.63ms 99% 1.03s 423444 requests in 10.10s, 50.88MB read Socket errors: connect 0, read 0, write 0, timeout 29 Requests/sec: 41926.32 Transfer/sec: 5.04MB
-
With reverse proxy
root@yusing-pc:~# wrk -t 10 -c 200 -d 10s -H "Host: bench.6uo.me" --latency http://10.0.1.7/bench Running 10s test @ http://10.0.1.7/bench 10 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 79.35ms 169.79ms 1.69s 92.55% Req/Sec 4.27k 1.90k 19.61k 75.81% Latency Distribution 50% 1.12ms 75% 105.66ms 90% 200.22ms 99% 814.59ms 409836 requests in 10.10s, 49.25MB read Socket errors: connect 0, read 0, write 0, timeout 18 Requests/sec: 40581.61 Transfer/sec: 4.88MB
Local benchmark (client running wrk and go-proxy
server are under same proxmox host but different LXCs)
-
Direct connection
root@http-benchmark-client:~# wrk -t 10 -c 200 -d 10s --latency http://10.0.100.1/bench Running 10s test @ http://10.0.100.1/bench 10 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 434.08us 539.35us 8.76ms 85.28% Req/Sec 67.71k 6.31k 87.21k 71.20% Latency Distribution 50% 153.00us 75% 646.00us 90% 1.18ms 99% 2.38ms 6739591 requests in 10.01s, 809.85MB read Requests/sec: 673608.15 Transfer/sec: 80.94MB
-
With
go-proxy
reverse proxyroot@http-benchmark-client:~# wrk -t 10 -c 200 -d 10s -H "Host: bench.6uo.me" --latency http://10.0.1.7/bench Running 10s test @ http://10.0.1.7/bench 10 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.23ms 0.96ms 11.43ms 72.09% Req/Sec 17.48k 1.76k 21.48k 70.20% Latency Distribution 50% 0.98ms 75% 1.76ms 90% 2.54ms 99% 4.24ms 1739079 requests in 10.01s, 208.97MB read Requests/sec: 173779.44 Transfer/sec: 20.88MB
-
With
traefik-v3
root@traefik-benchmark:~# wrk -t10 -c200 -d10s -H "Host: benchmark.whoami" --latency http://127.0.0.1:8000/bench Running 10s test @ http://127.0.0.1:8000/bench 10 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 2.81ms 10.36ms 180.26ms 98.57% Req/Sec 11.35k 1.74k 13.76k 85.54% Latency Distribution 50% 1.59ms 75% 2.27ms 90% 3.17ms 99% 37.91ms 1125723 requests in 10.01s, 109.50MB read Requests/sec: 112499.59 Transfer/sec: 10.94MB
Known issues
None
Memory usage
It takes ~13 MB for 50 proxy entries
Build it yourself
-
Install / Upgrade go (>=1.22) and
make
if not already -
Clear cache if you have built this before (go < 1.22) with
go clean -cache
-
get dependencies with
make get
-
build binary with
make build
-
start your container with
make up
(docker) orbin/go-proxy
(binary)