Easy to use reverse proxy with docker integration
Find a file
2024-03-27 06:33:02 +00:00
.vscode typos fix and url update for schema 2024-03-27 06:33:02 +00:00
bin fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
schema fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
screenshots fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
src/go-proxy fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
templates fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
.gitignore fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
.gitmodules fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
compose.example.yml fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
config.example.yml fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
Dockerfile fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
entrypoint.sh entrypoint fix for debugging and readme update 2024-03-22 15:39:23 +00:00
go.mod fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
go.sum fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
Makefile large refactoring, bug fixes, performance improvement 2024-03-18 04:51:59 +00:00
providers.example.yml fixes, meaningful error messages and new features 2024-03-27 06:30:47 +00:00
README.md typos fix and url update for schema 2024-03-27 06:33:02 +00:00
udp-test-server.Dockerfile scripts moved to makefile, tcp/udp connections can now close gracefully, but udp is still failing testing with palworld server 2024-03-04 19:09:36 +08:00

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

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)

    panel screenshot

  • Config editor to edit config and provider files with validation

    Validate and save file with Ctrl+S

    config editor screenshot

How to use

  1. Download and extract the latest release (or clone the repository if you want to try out experimental features)

  2. Copy config.example.yml to config/config.yml and modify the content to fit your needs

  3. (Optional) write your own config/providers.yml from providers.example.yml

  4. See Binary or docker

Binary

  1. (Optional) enabled HTTPS

    • Use autocert feature by completing autocert in config.yml

    • Use existing certificate

      Prepare your wildcard (*.y.z) SSL cert in certs/

      • cert / chain / fullchain: ./certs/cert.crt
      • private key: ./certs/priv.key
  2. run the binary bin/go-proxy

  3. enjoy

Docker

  1. Copy content from compose.example.yml and create your own compose.yml

  2. Add networks to make sure it is in the same network with other containers, or make sure proxy.<alias>.host is reachable

  3. (Optional) enable HTTPS

    • Use autocert feature

      1. mount ./certs to /app/certs

        go-proxy:
          ...
          volumes:
            - ./certs:/app/certs
        
      2. complete autocert in config.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
  4. Start go-proxy with docker compose up -d or make up.

  5. (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 under 100.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' -

  6. start your docker app, and visit <container_name>.y.z

  7. check the logs with docker compose logs or make 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
  • 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
  • host: proxy host

    • default: container_name
  • port: proxy port

    • default: first expose port (declared in Dockerfile or docker-compose.yml)
    • http(s): number in range og 0 - 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)
  • no_tls_verify: whether skip tls verify when scheme is https

    • default: false
  • 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
        1. apps.y.z/webdav -> webdav:80
        2. apps.y.z./webdav/path/to/file -> webdav:80/path/to/file
      • forward: path remain unchanged
        1. apps.y.z/webdav -> webdav:80/webdav
        2. 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 and action) and Javascript fetch(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

Environment variables

  • GOPROXY_DEBUG: set to 1 or true to enable debug behaviors (i.e. output, etc.)
  • GOPROXY_REDIRECT_HTTP: set to 0 or false to disable http to https redirect (only when certs are located)

Config File

See config.example.yml for more

Fields

  • autocert: autocert configuration

    • email: ACME Email
    • domains: a list of domains for cert registration
    • provider: DNS Challenge provider, see Supported DNS Challenge Providers
    • options: provider specific options
  • providers: reverse proxy providers configuration

    • kind: provider kind (string), see Provider Kinds
    • value: provider specific value

Provider Kinds

  • docker: load reverse proxies from docker

    values:

    • FROM_ENV: value from environment
    • full url to docker host (i.e. tcp://host:2375)
  • file: load reverse proxies from provider file

    value: 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 proxy

    root@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

  1. Install / Upgrade go (>=1.22) and make if not already

  2. Clear cache if you have built this before (go < 1.22) with go clean -cache

  3. get dependencies with make get

  4. build binary with make build

  5. start your container with make up (docker) or bin/go-proxy (binary)