This commit is contained in:
pini-gh 2023-03-26 22:25:12 +00:00 committed by GitHub
commit 290378eb2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 270 additions and 8 deletions

View File

@ -102,6 +102,8 @@ By default, docker uses IPv6-to-IPv4 NAT. This means all client connections from
If you need to support multiple virtual hosts for a container, you can separate each entry with commas. For example, `foo.bar.com,baz.bar.com,bar.com` and each host will be setup the same.
Do **not** put any space before of after each comma.
### Virtual Ports
When your container exposes only one port, nginx-proxy will default to this port, else to port 80.
@ -113,6 +115,71 @@ For each host defined into `VIRTUAL_HOST`, the associated virtual port is retrie
1. From the container's exposed port if there is only one
1. From the default port 80 when none of the above methods apply
### Multiport Syntax
There are services which expose more than one port. In this case you can set the `VIRTUAL_PORT` variable using multiport syntax:
```
VIRTUAL_PORT = [ <virtual_port> | <multiport> ];
multiport = port, { ",", port };
port = <virtual_port> [ ":", <virtual_path> [ ":", <virtual_dest> ]];
```
`<virtual_port>`, `<virtual_path>`, and `<virtual_dest>` accept the same values that `VIRTUAL_PORT`, `VIRTUAL_PATH`, and `VIRTUAL_DEST` do.
Example:
```
VIRTUAL_HOST: "multiport.example.com"
VIRTUAL_PORT: "9220:~ ^/(admin|fonts?|images|webmin)/,10901,20901:/ws2p,30901:/gva/playground"
```
would produce one nginx `upstream` definition per port, and as many `location` blocs:
```
# multiport.example.com:10901
upstream multiport.example.com-10901 {
## Can be connected with "docker-gen-bridge" network
# blah
server 172.29.0.5:10901;
}
# multiport.example.com:20901/ws2p
upstream multiport.example.com-5c7ebef820fe004e45e3af1d0c47971594d028b2-20901 {
## Can be connected with "docker-gen-bridge" network
# blah
server 172.29.0.5:20901;
}
# multiport.example.com:30901/gva/playground
upstream multiport.example.com-1f02ce2421b17d828edaabfc3014360891bb0be3-30901 {
## Can be connected with "docker-gen-bridge" network
# blah
server 172.29.0.5:30901;
}
# multiport.example.com:9220~ ^/(admin|fonts?|images|webmin)/
upstream multiport.example.com-cae8bfc2ea1fe6bb6fda08727ab065e8fed98aa2-9220 {
## Can be connected with "docker-gen-bridge" network
# blah
server 172.29.0.5:9220;
}
server {
server_name multiport.example.com;
listen 80 ;
access_log /var/log/nginx/access.log vhost;
location / {
proxy_pass http://multiport.example.com-10901;
}
location /ws2p {
proxy_pass http://multiport.example.com-5c7ebef820fe004e45e3af1d0c47971594d028b2-20901;
}
location /gva/playground {
proxy_pass http://multiport.example.com-1f02ce2421b17d828edaabfc3014360891bb0be3-30901;
}
location ~ ^/(admin|fonts?|images|webmin)/ {
proxy_pass http://multiport.example.com-cae8bfc2ea1fe6bb6fda08727ab065e8fed98aa2-9220;
}
}
```
As with the `VIRTUAL_PATH` it is possible to define per path location configuration files.
**Important note:** All `VIRTUAL_PATH` variables will be ignored for any virtual host appearing in a at least one container where `VIRTUAL_PORT` uses the multiport syntax, .
### Wildcard Hosts
You can also use wildcards at the beginning and the end of host name, like `*.bar.com` or `foo.bar.*`. Or even a regular expression, which can be very useful in conjunction with a wildcard DNS service like [nip.io](https://nip.io) or [sslip.io](https://sslip.io), using `~^foo\.bar\..*\.nip\.io` will match `foo.bar.127.0.0.1.nip.io`, `foo.bar.10.0.2.2.nip.io` and all other given IPs. More information about this topic can be found in the nginx documentation about [`server_names`](http://nginx.org/en/docs/http/server_names.html).

View File

@ -110,7 +110,8 @@
# exposed ports:{{ range sortObjectsByKeysAsc $.container.Addresses "Port" }} {{ .Port }}/{{ .Proto }}{{ else }} (none){{ end }}
{{- $default_port := when (eq (len $.container.Addresses) 1) (first $.container.Addresses).Port "80" }}
# default port: {{ $default_port }}
{{- $port := or $.container.Env.VIRTUAL_PORT $default_port }}
{{- $current_virtual_port := when (ne $.virtual_port "") $.virtual_port (coalesce $.container.Env.VIRTUAL_PORT "") }}
{{- $port := when (ne $current_virtual_port "") $current_virtual_port $default_port }}
# using port: {{ $port }}
{{- $addr_obj := where $.container.Addresses "Port" $port | first }}
{{- if and $addr_obj $addr_obj.HostPort }}
@ -179,7 +180,7 @@
include {{ $override }};
{{- else }}
{{- $keepalive := first (keys (groupByLabel .Containers "com.github.nginx-proxy.nginx-proxy.keepalive")) }}
location {{ .Path }} {
location {{ when (ne .Path "") .Path "/" }} {
{{- if eq .NetworkTag "internal" }}
# Only allow traffic from internal clients
include /etc/nginx/network_internal.conf;
@ -219,6 +220,7 @@
{{- end }}
{{- define "upstream" }}
{{- $virtual_port := .VirtualPort }}
upstream {{ .Upstream }} {
{{- $server_found := false }}
{{- $loadbalance := first (keys (groupByLabel .Containers "com.github.nginx-proxy.nginx-proxy.loadbalance")) }}
@ -231,7 +233,7 @@ upstream {{ .Upstream }} {
{{- $args := dict "globals" $.globals "container" $container }}
{{- template "container_ip" $args }}
{{- $ip := $args.ip }}
{{- $args := dict "container" $container }}
{{- $args := dict "container" $container "virtual_port" $virtual_port }}
{{- template "container_port" $args }}
{{- $port := $args.port }}
{{- if $ip }}
@ -353,7 +355,10 @@ proxy_set_header Proxy "";
* and whether there are any missing certs.
*/}}
{{- range $vhost, $containers := groupByMulti $globals.containers "Env.VIRTUAL_HOST" "," }}
{{- $vhost := trim $vhost }}
{{- if (ne (trim $vhost) $vhost) }}
# WARNING: virtual host '{{ $vhost }}' is not trimmed
{{- $vhost = trim $vhost }}
{{- end }}
{{- if not $vhost }}
{{- /* Ignore containers with VIRTUAL_HOST set to the empty string. */}}
{{- continue }}
@ -447,12 +452,40 @@ server {
{{- $is_regexp := hasPrefix "~" $host }}
{{- $upstream_name := when (or $is_regexp $globals.sha1_upstream_name) (sha1 $host) $host }}
{{- $paths := groupBy $containers "Env.VIRTUAL_PATH" }}
{{- /*
* Split containers between legacy VIRTUAL_PORT syntax and multiport one
*/}}
{{- $multiport_syntax_containers := list }}
{{- range $virtual_port, $vp_containers := groupBy $containers "Env.VIRTUAL_PORT" }}
{{- if (or (gt (len (split $virtual_port ":")) 1) (gt (len (split $virtual_port ",")) 1)) }}
{{- $multiport_syntax_containers = concat $multiport_syntax_containers $vp_containers }}
{{- range $container := $vp_containers }}
{{- $containers = without $containers $container }}
{{- end }}
{{- end }}
{{- end }}
{{- $virtualPorts := groupByMulti $multiport_syntax_containers "Env.VIRTUAL_PORT" "," }}
{{- $nVirtualPorts := len $virtualPorts }}
{{- /*
* Ignore VIRTUAL_PATH variables when using ultiport syntax
*/}}
{{- $paths := dict }}
{{- if (eq $nVirtualPorts 0) }}
{{- $paths = groupBy $containers "Env.VIRTUAL_PATH" }}
{{- end }}
{{- $nPaths := len $paths }}
{{- if eq $nPaths 0 }}
{{- /*
* No VIRTUAL_PORT, no VIRTUAL_PATH
*/}}
{{- if (and (eq $nPaths 0) (gt (len $containers) 0)) }}
{{- $paths = dict "/" $containers }}
{{- end }}
{{- /*
* Loops over VIRTUAL_PATHs
*/}}
{{- range $path, $containers := $paths }}
{{- $upstream := $upstream_name }}
{{- if gt $nPaths 0 }}
@ -460,7 +493,28 @@ server {
{{- $upstream = printf "%s-%s" $upstream $sum }}
{{- end }}
# {{ $host }}{{ $path }}
{{ template "upstream" (dict "globals" $globals "Upstream" $upstream "Containers" $containers) }}
{{- template "upstream" (dict "globals" $globals "Upstream" $upstream "Containers" $containers "VirtualPort" "") }}
{{- end }}
{{- /*
* Loops over multiport syntax containers
*/}}
{{- range $vp_spec, $containers := $virtualPorts }}
{{- $vp_data := split $vp_spec ":" }}
{{- $vp_port := index $vp_data 0 }}
{{- $vp_path := "" }}
{{- $upstream := "" }}
{{- if (ge (len $vp_data) 2) }}
{{- $vp_path = index $vp_data 1 }}
{{- end }}
{{- if (ne $vp_path "") }}
{{- $sum := sha1 $vp_path }}
{{- $upstream = printf "%s-%s-%s" $upstream_name $sum $vp_port }}
{{- else }}
{{- $upstream = printf "%s-%s" $upstream_name $vp_port }}
{{- end }}
# {{ $host }}:{{ $vp_port }}{{ $vp_path }}
{{- template "upstream" (dict "globals" $globals "Upstream" $upstream "Containers" $containers "VirtualPort" $vp_port)}}
{{- end }}
{{- /*
@ -597,6 +651,7 @@ server {
include /etc/nginx/vhost.d/default;
{{- end }}
{{- $needs_default_root_response := ne $globals.default_root_response "none" }}
{{- range $path, $containers := $paths }}
{{- /*
* Get the VIRTUAL_PROTO defined by containers w/ the same
@ -617,8 +672,47 @@ server {
{{- $dest = (or (first (groupByKeys $containers "Env.VIRTUAL_DEST")) "") }}
{{- end }}
{{- template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VhostRoot" $vhost_root "Dest" $dest "NetworkTag" $network_tag "Containers" $containers) }}
{{- if (eq $path "/") }}
{{- $needs_default_root_response = false }}
{{- end }}
{{- end }}
{{- if and (not (contains $paths "/")) (ne $globals.default_root_response "none")}}
{{- range $vp_spec, $containers := $virtualPorts }}
{{- $vp_data := split $vp_spec ":" }}
{{- $vp_port := index $vp_data 0 }}
{{- $vp_path := "" }}
{{- $vp_dest := "" }}
{{- $upstream := "" }}
{{- if (ge (len $vp_data) 2) }}
{{- $vp_path = index $vp_data 1 }}
{{- if (ge (len $vp_data) 3) }}
{{- $vp_dest = index $vp_data 2 }}
{{- end }}
{{- end }}
{{- if (ne $vp_path "") }}
{{- $sum := sha1 $vp_path }}
{{- $upstream = printf "%s-%s-%s" $upstream_name $sum $vp_port }}
{{- else }}
{{- $upstream = printf "%s-%s" $upstream_name $vp_port }}
{{- end }}
{{- if (or (eq $vp_path "") (eq $vp_path "/")) }}
{{- $needs_default_root_response = false }}
{{- end }}
{{- /*
* Get the VIRTUAL_PROTO defined by containers w/ the same
* vhost-vpath, falling back to "http".
*/}}
{{- $proto := trim (or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http") }}
{{- /*
* Get the NETWORK_ACCESS defined by containers w/ the same vhost,
* falling back to "external".
*/}}
{{- $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }}
{{- template "location" (dict "Path" $vp_path "Proto" $proto "Upstream" $upstream "Host" $host "VhostRoot" $vhost_root "Dest" $vp_dest "NetworkTag" $network_tag "Containers" $containers) }}
{{- end }}
{{- if $needs_default_root_response }}
location / {
return {{ $globals.default_root_response }};
}

View File

@ -0,0 +1,29 @@
import pytest
def test_web_no_slash_location(docker_compose, nginxproxy):
r = nginxproxy.get("http://web.nginx-proxy.tld/")
assert r.status_code == 405
def test_web_rout_to_slash_port(docker_compose, nginxproxy):
r = nginxproxy.get("http://web.nginx-proxy.tld/which-port")
assert r.status_code == 200
assert "answer from port 83\n" in r.text
def test_web1_answers_on_slash_location(docker_compose, nginxproxy):
r = nginxproxy.get("http://web1.nginx-proxy.tld/")
assert r.status_code == 200
def test_web1_no_virtual_path(docker_compose, nginxproxy):
r = nginxproxy.get("http://web1.nginx-proxy.tld/which-port")
assert r.status_code == 404
def test_web1_port_80_is_served_by_location_slash_80(docker_compose, nginxproxy):
r = nginxproxy.get("http://web1.nginx-proxy.tld/80/port")
assert r.status_code == 200
assert "answer from port 80\n" in r.text
def test_web1_port_81_is_served_by_location_slash_81(docker_compose, nginxproxy):
r = nginxproxy.get("http://web1.nginx-proxy.tld/81/port")
assert r.status_code == 200
assert "answer from port 81\n" in r.text

View File

@ -0,0 +1,28 @@
web:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: "web.nginx-proxy.tld,web1.nginx-proxy.tld"
VIRTUAL_PORT: "83"
VIRTUAL_PATH: "/which-port"
VIRTUAL_DEST: "/port"
web1:
image: web
expose:
- "80"
- "81"
environment:
WEB_PORTS: "80 81"
VIRTUAL_HOST: "web1.nginx-proxy.tld"
VIRTUAL_PORT: "80:/80:/,81:/81:/"
sut:
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./web.nginx-proxy.tld_022792f37cc2c58102bdf79316aebed215e0de21_location:/etc/nginx/vhost.d/web.nginx-proxy.tld_022792f37cc2c58102bdf79316aebed215e0de21_location
environment:
DEFAULT_ROOT: "405"

View File

@ -0,0 +1,26 @@
import pytest
def test_port_80_is_server_by_location_root(docker_compose, nginxproxy):
r = nginxproxy.get("http://web.nginx-proxy.tld/port")
assert r.status_code == 200
assert "answer from port 80\n" in r.text
def test_port_81_is_server_by_location_slash81(docker_compose, nginxproxy):
r = nginxproxy.get("http://web.nginx-proxy.tld/81/port")
assert r.status_code == 200
assert "answer from port 81\n" in r.text
def test_port_82_is_server_by_location_slash82_with_dest_slashport(docker_compose, nginxproxy):
r = nginxproxy.get("http://web.nginx-proxy.tld/82")
assert r.status_code == 200
assert "answer from port 82\n" in r.text
def test_port_83_is_server_by_regex_location_slash83_with_rewrite_in_custom_location_file(docker_compose, nginxproxy):
# The custom location file with rewrite is requested because when
# location is specified using a regex then proxy_pass should be
# specified without a URI
# see http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
r = nginxproxy.get("http://web.nginx-proxy.tld/83/port")
assert r.status_code == 200
assert "answer from port 83\n" in r.text

View File

@ -0,0 +1,17 @@
web:
image: web
expose:
- "80"
- "81"
- "82"
- "83"
environment:
WEB_PORTS: "80 81 82 83"
VIRTUAL_HOST: "web.nginx-proxy.tld"
VIRTUAL_PORT: "80,81:/81:/,82:/82:/port,83:~ ^/[8][3]"
sut:
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./web.nginx-proxy.tld_022792f37cc2c58102bdf79316aebed215e0de21_location:/etc/nginx/vhost.d/web.nginx-proxy.tld_022792f37cc2c58102bdf79316aebed215e0de21_location

View File

@ -0,0 +1 @@
rewrite ^/83/(.*)$ /$1 break;