mirror of
https://github.com/thib8956/nginx-proxy
synced 2025-02-24 01:38:15 +00:00
Merge pull request #2434 from nginx-proxy/multiport-support
feat: multiport support
This commit is contained in:
commit
db07d90ad8
@ -54,6 +54,105 @@ 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 container's exposed port if there is only one
|
||||||
1. From the default port 80 when none of the above methods apply
|
1. From the default port 80 when none of the above methods apply
|
||||||
|
|
||||||
|
### Multiple ports
|
||||||
|
|
||||||
|
If your container expose more than one service on different ports and those services need to be proxied, you'll need to use the `VIRTUAL_HOST_MULTIPORTS` environment variable. This variable takes virtual host, path, port and dest definition in YAML (or JSON) form, and completely override the `VIRTUAL_HOST`, `VIRTUAL_PORT`, `VIRTUAL_PATH` and `VIRTUAL_DEST` environment variables on this container.
|
||||||
|
|
||||||
|
The YAML syntax should be easier to write on Docker compose files, while the JSON syntax can be used for CLI invocation.
|
||||||
|
|
||||||
|
The expected format is the following:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
hostname:
|
||||||
|
path:
|
||||||
|
port: int
|
||||||
|
dest: string
|
||||||
|
```
|
||||||
|
|
||||||
|
For each hostname entry, `path`, `port` and `dest` are optional and are assigned default values when missing:
|
||||||
|
|
||||||
|
- `path` = "/"
|
||||||
|
- `port` = default port
|
||||||
|
- `dest` = ""
|
||||||
|
|
||||||
|
The following examples use an hypothetical container running services on port 80, 8000 and 9000:
|
||||||
|
|
||||||
|
#### Multiple ports routed to different hostnames
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
multiport-container:
|
||||||
|
image: somerepo/somecontainer
|
||||||
|
container_name: multiport-container
|
||||||
|
environment:
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
www.example.org:
|
||||||
|
service1.example.org:
|
||||||
|
"/":
|
||||||
|
port: 8000
|
||||||
|
service2.example.org:
|
||||||
|
"/":
|
||||||
|
port: 9000
|
||||||
|
|
||||||
|
# There is no path dict specified for www.example.org, so it get the default values:
|
||||||
|
# www.example.org:
|
||||||
|
# "/":
|
||||||
|
# port: 80 (default port)
|
||||||
|
# dest: ""
|
||||||
|
|
||||||
|
# JSON equivalent:
|
||||||
|
# VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
# {
|
||||||
|
# "www.example.org": {},
|
||||||
|
# "service1.example.org": { "/": { "port": 8000, "dest": "" } },
|
||||||
|
# "service2.example.org": { "/": { "port": 9000, "dest": "" } }
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
This would result in the following proxy config:
|
||||||
|
|
||||||
|
- `www.example.org` -> `multiport-container:80`
|
||||||
|
- `service1.example.org` -> `multiport-container:8000`
|
||||||
|
- `service2.example.org` -> `multiport-container:9000`
|
||||||
|
|
||||||
|
#### Multiple ports routed to same hostname and different paths
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
multiport-container:
|
||||||
|
image: somerepo/somecontainer
|
||||||
|
container_name: multiport-container
|
||||||
|
environment:
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
www.example.org:
|
||||||
|
"/":
|
||||||
|
"/service1":
|
||||||
|
port: 8000
|
||||||
|
dest: "/"
|
||||||
|
"/service2":
|
||||||
|
port: 9000
|
||||||
|
dest: "/"
|
||||||
|
|
||||||
|
# port and dest are not specified on the / path, so this path is routed
|
||||||
|
# to the default port with the default dest value (empty string)
|
||||||
|
|
||||||
|
# JSON equivalent:
|
||||||
|
# VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
# {
|
||||||
|
# "www.example.org": {
|
||||||
|
# "/": {},
|
||||||
|
# "/service1": { "port": 8000, "dest": "/" },
|
||||||
|
# "/service2": { "port": 9000, "dest": "/" }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
This would result in the following proxy config:
|
||||||
|
|
||||||
|
- `www.example.org` -> `multiport-container:80`
|
||||||
|
- `www.example.org/service1` -> `multiport-container:8000`
|
||||||
|
- `www.example.org/service2` -> `multiport-container:9000`
|
||||||
|
|
||||||
⬆️ [back to table of contents](#table-of-contents)
|
⬆️ [back to table of contents](#table-of-contents)
|
||||||
|
|
||||||
## Path-based Routing
|
## Path-based Routing
|
||||||
|
220
nginx.tmpl
220
nginx.tmpl
@ -128,7 +128,7 @@
|
|||||||
# exposed ports:{{ range sortObjectsByKeysAsc $.container.Addresses "Port" }} {{ .Port }}/{{ .Proto }}{{ else }} (none){{ end }}
|
# 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 := when (eq (len $.container.Addresses) 1) (first $.container.Addresses).Port "80" }}
|
||||||
# default port: {{ $default_port }}
|
# default port: {{ $default_port }}
|
||||||
{{- $port := when (eq $.port "legacy") (or $.container.Env.VIRTUAL_PORT $default_port) $.port }}
|
{{- $port := when (eq $.port "default") $default_port (when (eq $.port "legacy") (or $.container.Env.VIRTUAL_PORT $default_port) $.port) }}
|
||||||
# using port: {{ $port }}
|
# using port: {{ $port }}
|
||||||
{{- $addr_obj := where $.container.Addresses "Port" $port | first }}
|
{{- $addr_obj := where $.container.Addresses "Port" $port | first }}
|
||||||
{{- if and $addr_obj $addr_obj.HostPort }}
|
{{- if and $addr_obj $addr_obj.HostPort }}
|
||||||
@ -338,49 +338,6 @@ upstream {{ $vpath.upstream }} {
|
|||||||
}
|
}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- /*
|
|
||||||
* Template used as a function to collect virtual path properties from
|
|
||||||
* the given containers. These properties are "returned" by storing their
|
|
||||||
* values into the provided dot dict.
|
|
||||||
*
|
|
||||||
* The provided dot dict is expected to have the following entries:
|
|
||||||
* - "Containers": List of container's RuntimeContainer struct.
|
|
||||||
* - "Upstream_name"
|
|
||||||
* - "Has_virtual_paths": boolean
|
|
||||||
* - "Path"
|
|
||||||
*
|
|
||||||
* The return values will be added to the dot dict with keys:
|
|
||||||
* - "dest"
|
|
||||||
* - "proto"
|
|
||||||
* - "network_tag"
|
|
||||||
* - "upstream"
|
|
||||||
* - "loadbalance"
|
|
||||||
* - "keepalive"
|
|
||||||
*/}}
|
|
||||||
{{- define "get_path_info" }}
|
|
||||||
{{- /* 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" }}
|
|
||||||
|
|
||||||
{{- $loadbalance := first (keys (groupByLabel $.Containers "com.github.nginx-proxy.nginx-proxy.loadbalance")) }}
|
|
||||||
{{- $keepalive := coalesce (first (keys (groupByLabel $.Containers "com.github.nginx-proxy.nginx-proxy.keepalive"))) "disabled" }}
|
|
||||||
|
|
||||||
{{- $upstream := $.Upstream_name }}
|
|
||||||
{{- $dest := "" }}
|
|
||||||
{{- if $.Has_virtual_paths }}
|
|
||||||
{{- $sum := sha1 $.Path }}
|
|
||||||
{{- $upstream = printf "%s-%s" $upstream $sum }}
|
|
||||||
{{- $dest = or (first (groupByKeys $.Containers "Env.VIRTUAL_DEST")) "" }}
|
|
||||||
{{- end }}
|
|
||||||
{{- $_ := set $ "proto" $proto }}
|
|
||||||
{{- $_ := set $ "network_tag" $network_tag }}
|
|
||||||
{{- $_ := set $ "upstream" $upstream }}
|
|
||||||
{{- $_ := set $ "dest" $dest }}
|
|
||||||
{{- $_ := set $ "loadbalance" $loadbalance }}
|
|
||||||
{{- $_ := set $ "keepalive" $keepalive }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
|
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
|
||||||
# scheme used to connect to this server
|
# scheme used to connect to this server
|
||||||
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
||||||
@ -500,15 +457,131 @@ proxy_set_header X-Original-URI $request_uri;
|
|||||||
proxy_set_header Proxy "";
|
proxy_set_header Proxy "";
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- /* Precompute some information about each vhost. */}}
|
{{- /* Precompute and store some information about vhost that use VIRTUAL_HOST_MULTIPORTS. */}}
|
||||||
{{- range $hostname, $containers := groupByMulti $globals.containers "Env.VIRTUAL_HOST" "," }}
|
{{- range $vhosts_yaml, $containers := groupBy $globals.containers "Env.VIRTUAL_HOST_MULTIPORTS" }}
|
||||||
{{- $hostname = trim $hostname }}
|
{{- /* Print a warning in the config if VIRTUAL_HOST_MULTIPORTS can't be parsed. */}}
|
||||||
{{- if not $hostname }}
|
{{- $parsedVhosts := fromYaml $vhosts_yaml }}
|
||||||
{{- /* Ignore containers with VIRTUAL_HOST set to the empty string. */}}
|
{{- if (empty $parsedVhosts) }}
|
||||||
|
{{- $containerNames := list }}
|
||||||
|
{{- range $container := $containers }}
|
||||||
|
{{- $containerNames = append $containerNames $container.Name }}
|
||||||
|
{{- end }}
|
||||||
|
# /!\ WARNING: the VIRTUAL_HOST_MULTIPORTS environment variable used for {{ len $containerNames | plural "this container" "those containers" }} is not a valid YAML string:
|
||||||
|
# {{ $containerNames | join ", " }}
|
||||||
{{- continue }}
|
{{- continue }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- $certName := first (groupByKeys $containers "Env.CERT_NAME") }}
|
{{- range $hostname, $vhost := $parsedVhosts }}
|
||||||
|
{{- $vhost_data := when (hasKey $globals.vhosts $hostname) (get $globals.vhosts $hostname) (dict) }}
|
||||||
|
{{- $paths := coalesce $vhost_data.paths (dict) }}
|
||||||
|
|
||||||
|
{{- if (empty $vhost) }}
|
||||||
|
{{ $vhost = dict "/" (dict) }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- range $path, $vpath := $vhost }}
|
||||||
|
{{- if (empty $vpath) }}
|
||||||
|
{{- $vpath = dict "dest" "" "port" "default" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- $dest := coalesce $vpath.dest "" }}
|
||||||
|
{{- $port := when (hasKey $vpath "port") (toString $vpath.port) "default" }}
|
||||||
|
{{- $path_data := when (hasKey $paths $path) (get $paths $path) (dict) }}
|
||||||
|
{{- $path_ports := when (hasKey $path_data "ports") (get $path_data "ports") (dict) }}
|
||||||
|
{{- $path_port_containers := when (hasKey $path_ports $port) (get $path_ports $port) (list) }}
|
||||||
|
{{- $path_port_containers = concat $path_port_containers $containers }}
|
||||||
|
{{- $_ := set $path_ports $port $path_port_containers }}
|
||||||
|
{{- $_ := set $path_data "ports" $path_ports }}
|
||||||
|
{{- if (not (hasKey $path_data "dest")) }}
|
||||||
|
{{- $_ := set $path_data "dest" $dest }}
|
||||||
|
{{- end }}
|
||||||
|
{{- $_ := set $paths $path $path_data }}
|
||||||
|
{{- end }}
|
||||||
|
{{- $_ := set $vhost_data "paths" $paths }}
|
||||||
|
{{- $is_regexp := hasPrefix "~" $hostname }}
|
||||||
|
{{- $_ := set $vhost_data "upstream_name" (when (or $is_regexp $globals.sha1_upstream_name) (sha1 $hostname) $hostname) }}
|
||||||
|
{{- $_ := set $globals.vhosts $hostname $vhost_data }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- /* Precompute and store some information about vhost that use VIRTUAL_HOST. */}}
|
||||||
|
{{- range $hostname, $containers := groupByMulti $globals.containers "Env.VIRTUAL_HOST" "," }}
|
||||||
|
{{- /* Ignore containers with VIRTUAL_HOST set to the empty string. */}}
|
||||||
|
{{- $hostname = trim $hostname }}
|
||||||
|
{{- if not $hostname }}
|
||||||
|
{{- continue }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- /* Drop containers with both VIRTUAL_HOST and VIRTUAL_HOST_MULTIPORTS set
|
||||||
|
* (VIRTUAL_HOST_MULTIPORTS takes precedence thanks to the previous loop).
|
||||||
|
*/}}
|
||||||
|
{{- range $_, $containers_to_drop := groupBy $containers "Env.VIRTUAL_HOST_MULTIPORTS" }}
|
||||||
|
{{- range $container := $containers_to_drop }}
|
||||||
|
{{- $containers = without $containers $container }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if (eq (len $containers) 0) }}
|
||||||
|
{{- continue }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- $vhost_data := when (hasKey $globals.vhosts $hostname) (get $globals.vhosts $hostname) (dict) }}
|
||||||
|
{{- $paths := coalesce $vhost_data.paths (dict) }}
|
||||||
|
|
||||||
|
{{- $tmp_paths := groupByWithDefault $containers "Env.VIRTUAL_PATH" "/" }}
|
||||||
|
|
||||||
|
{{- range $path, $containers := $tmp_paths }}
|
||||||
|
{{- $dest := or (first (groupByKeys $containers "Env.VIRTUAL_DEST")) "" }}
|
||||||
|
{{- $port := "legacy" }}
|
||||||
|
{{- $path_data := when (hasKey $paths $path) (get $paths $path) (dict) }}
|
||||||
|
{{- $path_ports := when (hasKey $path_data "ports") (get $path_data "ports") (dict) }}
|
||||||
|
{{- $path_port_containers := when (hasKey $path_ports $port) (get $path_ports $port) (list) }}
|
||||||
|
{{- $path_port_containers = concat $path_port_containers $containers }}
|
||||||
|
{{- $_ := set $path_ports $port $path_port_containers }}
|
||||||
|
{{- $_ := set $path_data "ports" $path_ports }}
|
||||||
|
{{- if (not (hasKey $path_data "dest")) }}
|
||||||
|
{{- $_ := set $path_data "dest" $dest }}
|
||||||
|
{{- end }}
|
||||||
|
{{- $_ := set $paths $path $path_data }}
|
||||||
|
{{- end }}
|
||||||
|
{{- $_ := set $vhost_data "paths" $paths }}
|
||||||
|
{{- $is_regexp := hasPrefix "~" $hostname }}
|
||||||
|
{{- $_ := set $vhost_data "upstream_name" (when (or $is_regexp $globals.sha1_upstream_name) (sha1 $hostname) $hostname) }}
|
||||||
|
{{- $_ := set $globals.vhosts $hostname $vhost_data }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- /* Loop over $globals.vhosts and update it with the remaining informations about each vhost. */}}
|
||||||
|
{{- range $hostname, $vhost_data := $globals.vhosts }}
|
||||||
|
{{- $vhost_containers := list }}
|
||||||
|
{{- range $path, $vpath_data := $vhost_data.paths }}
|
||||||
|
{{- $vpath_containers := list }}
|
||||||
|
{{- range $port, $vport_containers := $vpath_data.ports }}
|
||||||
|
{{ $vpath_containers = concat $vpath_containers $vport_containers }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- /* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http". */}}
|
||||||
|
{{- $proto := trim (or (first (groupByKeys $vpath_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 $vpath_containers "Env.NETWORK_ACCESS")) "external" }}
|
||||||
|
|
||||||
|
{{- $loadbalance := first (keys (groupByLabel $vpath_containers "com.github.nginx-proxy.nginx-proxy.loadbalance")) }}
|
||||||
|
{{- $keepalive := coalesce (first (keys (groupByLabel $vpath_containers "com.github.nginx-proxy.nginx-proxy.keepalive"))) "disabled" }}
|
||||||
|
|
||||||
|
{{- $upstream := $vhost_data.upstream_name }}
|
||||||
|
{{- if (not (eq $path "/")) }}
|
||||||
|
{{- $sum := sha1 $path }}
|
||||||
|
{{- $upstream = printf "%s-%s" $upstream $sum }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- $_ := set $vpath_data "proto" $proto }}
|
||||||
|
{{- $_ := set $vpath_data "network_tag" $network_tag }}
|
||||||
|
{{- $_ := set $vpath_data "upstream" $upstream }}
|
||||||
|
{{- $_ := set $vpath_data "loadbalance" $loadbalance }}
|
||||||
|
{{- $_ := set $vpath_data "keepalive" $keepalive }}
|
||||||
|
{{- $_ := set $vhost_data.paths $path $vpath_data }}
|
||||||
|
|
||||||
|
{{ $vhost_containers = concat $vhost_containers $vpath_containers }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- $certName := first (groupByKeys $vhost_containers "Env.CERT_NAME") }}
|
||||||
{{- $vhostCert := closest (dir "/etc/nginx/certs") (printf "%s.crt" $hostname) }}
|
{{- $vhostCert := closest (dir "/etc/nginx/certs") (printf "%s.crt" $hostname) }}
|
||||||
{{- $vhostCert = trimSuffix ".crt" $vhostCert }}
|
{{- $vhostCert = trimSuffix ".crt" $vhostCert }}
|
||||||
{{- $vhostCert = trimSuffix ".key" $vhostCert }}
|
{{- $vhostCert = trimSuffix ".key" $vhostCert }}
|
||||||
@ -516,49 +589,23 @@ proxy_set_header Proxy "";
|
|||||||
{{- $cert_ok := and (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert)) }}
|
{{- $cert_ok := and (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert)) }}
|
||||||
|
|
||||||
{{- $default := eq $globals.Env.DEFAULT_HOST $hostname }}
|
{{- $default := eq $globals.Env.DEFAULT_HOST $hostname }}
|
||||||
{{- $https_method := or (first (groupByKeys $containers "Env.HTTPS_METHOD")) $globals.Env.HTTPS_METHOD "redirect" }}
|
{{- $https_method := or (first (groupByKeys $vhost_containers "Env.HTTPS_METHOD")) $globals.Env.HTTPS_METHOD "redirect" }}
|
||||||
{{- $http2_enabled := parseBool (or (first (keys (groupByLabel $containers "com.github.nginx-proxy.nginx-proxy.http2.enable"))) $globals.Env.ENABLE_HTTP2 "true")}}
|
{{- $http2_enabled := parseBool (or (first (keys (groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http2.enable"))) $globals.Env.ENABLE_HTTP2 "true")}}
|
||||||
{{- $http3_enabled := parseBool (or (first (keys (groupByLabel $containers "com.github.nginx-proxy.nginx-proxy.http3.enable"))) $globals.Env.ENABLE_HTTP3 "false")}}
|
{{- $http3_enabled := parseBool (or (first (keys (groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http3.enable"))) $globals.Env.ENABLE_HTTP3 "false")}}
|
||||||
|
|
||||||
{{- $is_regexp := hasPrefix "~" $hostname }}
|
|
||||||
{{- $upstream_name := when (or $is_regexp $globals.sha1_upstream_name) (sha1 $hostname) $hostname }}
|
|
||||||
|
|
||||||
{{- /* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "". */}}
|
{{- /* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "". */}}
|
||||||
{{- $server_tokens := trim (or (first (groupByKeys $containers "Env.SERVER_TOKENS")) "") }}
|
{{- $server_tokens := trim (or (first (groupByKeys $vhost_containers "Env.SERVER_TOKENS")) "") }}
|
||||||
|
|
||||||
{{- /* Get the SSL_POLICY defined by containers w/ the same vhost, falling back to empty string (use default). */}}
|
{{- /* Get the SSL_POLICY defined by containers w/ the same vhost, falling back to empty string (use default). */}}
|
||||||
{{- $ssl_policy := or (first (groupByKeys $containers "Env.SSL_POLICY")) "" }}
|
{{- $ssl_policy := or (first (groupByKeys $vhost_containers "Env.SSL_POLICY")) "" }}
|
||||||
|
|
||||||
{{- /* Get the HSTS defined by containers w/ the same vhost, falling back to "max-age=31536000". */}}
|
{{- /* Get the HSTS defined by containers w/ the same vhost, falling back to "max-age=31536000". */}}
|
||||||
{{- $hsts := or (first (groupByKeys $containers "Env.HSTS")) (or $globals.Env.HSTS "max-age=31536000") }}
|
{{- $hsts := or (first (groupByKeys $vhost_containers "Env.HSTS")) (or $globals.Env.HSTS "max-age=31536000") }}
|
||||||
|
|
||||||
{{- /* Get the VIRTUAL_ROOT By containers w/ use fastcgi root */}}
|
{{- /* Get the VIRTUAL_ROOT By containers w/ use fastcgi root */}}
|
||||||
{{- $vhost_root := or (first (groupByKeys $containers "Env.VIRTUAL_ROOT")) "/var/www/public" }}
|
{{- $vhost_root := or (first (groupByKeys $vhost_containers "Env.VIRTUAL_ROOT")) "/var/www/public" }}
|
||||||
|
|
||||||
|
{{- $vhost_data = merge $vhost_data (dict
|
||||||
{{- $tmp_paths := groupBy $containers "Env.VIRTUAL_PATH" }}
|
|
||||||
{{- $has_virtual_paths := gt (len $tmp_paths) 0}}
|
|
||||||
{{- if not $has_virtual_paths }}
|
|
||||||
{{- $tmp_paths = dict "/" $containers }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- $paths := dict }}
|
|
||||||
|
|
||||||
{{- range $path, $containers := $tmp_paths }}
|
|
||||||
{{- $args := dict "Containers" $containers "Path" $path "Upstream_name" $upstream_name "Has_virtual_paths" $has_virtual_paths }}
|
|
||||||
{{- template "get_path_info" $args }}
|
|
||||||
{{- $_ := set $paths $path (dict
|
|
||||||
"ports" (dict "legacy" $containers)
|
|
||||||
"dest" $args.dest
|
|
||||||
"proto" $args.proto
|
|
||||||
"network_tag" $args.network_tag
|
|
||||||
"upstream" $args.upstream
|
|
||||||
"loadbalance" $args.loadbalance
|
|
||||||
"keepalive" $args.keepalive
|
|
||||||
) }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- $_ := set $globals.vhosts $hostname (dict
|
|
||||||
"cert" $cert
|
"cert" $cert
|
||||||
"cert_ok" $cert_ok
|
"cert_ok" $cert_ok
|
||||||
"default" $default
|
"default" $default
|
||||||
@ -566,13 +613,14 @@ proxy_set_header Proxy "";
|
|||||||
"https_method" $https_method
|
"https_method" $https_method
|
||||||
"http2_enabled" $http2_enabled
|
"http2_enabled" $http2_enabled
|
||||||
"http3_enabled" $http3_enabled
|
"http3_enabled" $http3_enabled
|
||||||
"paths" $paths
|
|
||||||
"server_tokens" $server_tokens
|
"server_tokens" $server_tokens
|
||||||
"ssl_policy" $ssl_policy
|
"ssl_policy" $ssl_policy
|
||||||
"vhost_root" $vhost_root
|
"vhost_root" $vhost_root
|
||||||
) }}
|
) }}
|
||||||
|
{{- $_ := set $globals.vhosts $hostname $vhost_data }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
|
||||||
{{- /*
|
{{- /*
|
||||||
* If needed, create a catch-all fallback server to send an error code to
|
* If needed, create a catch-all fallback server to send an error code to
|
||||||
* clients that request something from an unknown vhost.
|
* clients that request something from an unknown vhost.
|
||||||
@ -808,4 +856,4 @@ server {
|
|||||||
}
|
}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
}
|
}
|
||||||
{{- end }}
|
{{- end }}
|
@ -1,10 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
def test_unknown_virtual_host(docker_compose, nginxproxy):
|
|
||||||
r = nginxproxy.get("http://nginx-proxy/")
|
|
||||||
assert r.status_code == 503
|
|
||||||
|
|
||||||
def test_forwards_to_whoami(docker_compose, nginxproxy):
|
|
||||||
r = nginxproxy.get("http://web.nginx-proxy.example/port")
|
|
||||||
assert r.status_code == 200
|
|
||||||
assert r.text == "answer from port 81\n"
|
|
@ -1,15 +0,0 @@
|
|||||||
version: "2"
|
|
||||||
|
|
||||||
services:
|
|
||||||
nginx-proxy:
|
|
||||||
image: nginxproxy/nginx-proxy:test
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
|
||||||
|
|
||||||
web:
|
|
||||||
image: web
|
|
||||||
expose:
|
|
||||||
- "81"
|
|
||||||
environment:
|
|
||||||
WEB_PORTS: 81
|
|
||||||
VIRTUAL_HOST: web.nginx-proxy.example
|
|
39
test/test_multiports/test_multiports-base-json.py
Normal file
39
test/test_multiports/test_multiports-base-json.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_virtual_host_is_dropped_when_using_multiports(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://notskipped.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 81\n" in r.text
|
||||||
|
r = nginxproxy.get("http://skipped.nginx-proxy.tld/")
|
||||||
|
assert r.status_code == 503
|
||||||
|
|
||||||
|
|
||||||
|
def test_answer_is_served_from_port_80_by_default(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://port80.a.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 80\n" in r.text
|
||||||
|
r = nginxproxy.get("http://port80.b.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 80\n" in r.text
|
||||||
|
r = nginxproxy.get("http://port80.c.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 80\n" in r.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_answer_is_served_from_chosen_ports(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://port8080.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 8080\n" in r.text
|
||||||
|
r = nginxproxy.get("http://port9000.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 9000\n" in r.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_answer_is_served_from_chosen_ports_and_dest(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://virtualpaths.nginx-proxy.tld/rootdest/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 10001\n" in r.text
|
||||||
|
r = nginxproxy.get("http://virtualpaths.nginx-proxy.tld/customdest")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 10002\n" in r.text
|
77
test/test_multiports/test_multiports-base-json.yml
Normal file
77
test/test_multiports/test_multiports-base-json.yml
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
version: "2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
skipvirtualhost:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81"
|
||||||
|
VIRTUAL_HOST: skipped.nginx-proxy.tld
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
{
|
||||||
|
"notskipped.nginx-proxy.tld": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultport:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "80"
|
||||||
|
- "8080"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "80 8080"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
{
|
||||||
|
"port80.a.nginx-proxy.tld": {},
|
||||||
|
"port80.b.nginx-proxy.tld": {},
|
||||||
|
"port80.c.nginx-proxy.tld": {
|
||||||
|
"/": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
multiports:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
|
- "9000"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "8080 9000"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
{
|
||||||
|
"port8080.nginx-proxy.tld": {
|
||||||
|
"/": {
|
||||||
|
"port": 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"port9000.nginx-proxy.tld": {
|
||||||
|
"/": {
|
||||||
|
"port": 9000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualpath:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "10001"
|
||||||
|
- "10002"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "10001 10002"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
{
|
||||||
|
"virtualpaths.nginx-proxy.tld": {
|
||||||
|
"/rootdest": {
|
||||||
|
"port": 10001,
|
||||||
|
"dest": "/"
|
||||||
|
},
|
||||||
|
"/customdest": {
|
||||||
|
"port": 10002,
|
||||||
|
"dest": "/port"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
39
test/test_multiports/test_multiports-base-yaml.py
Normal file
39
test/test_multiports/test_multiports-base-yaml.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_virtual_host_is_dropped_when_using_multiports(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://notskipped.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 81\n" in r.text
|
||||||
|
r = nginxproxy.get("http://skipped.nginx-proxy.tld/")
|
||||||
|
assert r.status_code == 503
|
||||||
|
|
||||||
|
|
||||||
|
def test_answer_is_served_from_port_80_by_default(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://port80.a.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 80\n" in r.text
|
||||||
|
r = nginxproxy.get("http://port80.b.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 80\n" in r.text
|
||||||
|
r = nginxproxy.get("http://port80.c.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 80\n" in r.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_answer_is_served_from_chosen_ports(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://port8080.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 8080\n" in r.text
|
||||||
|
r = nginxproxy.get("http://port9000.nginx-proxy.tld/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 9000\n" in r.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_answer_is_served_from_chosen_ports_and_dest(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://virtualpaths.nginx-proxy.tld/rootdest/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 10001\n" in r.text
|
||||||
|
r = nginxproxy.get("http://virtualpaths.nginx-proxy.tld/customdest")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 10002\n" in r.text
|
61
test/test_multiports/test_multiports-base-yaml.yml
Normal file
61
test/test_multiports/test_multiports-base-yaml.yml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
version: "2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
skipvirtualhost:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81"
|
||||||
|
VIRTUAL_HOST: skipped.nginx-proxy.tld
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
notskipped.nginx-proxy.tld:
|
||||||
|
|
||||||
|
defaultport:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "80"
|
||||||
|
- "8080"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "80 8080"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
port80.a.nginx-proxy.tld:
|
||||||
|
port80.b.nginx-proxy.tld:
|
||||||
|
port80.c.nginx-proxy.tld:
|
||||||
|
"/":
|
||||||
|
|
||||||
|
multiports:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
|
- "9000"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "8080 9000"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
port8080.nginx-proxy.tld:
|
||||||
|
"/":
|
||||||
|
port: 8080
|
||||||
|
port9000.nginx-proxy.tld:
|
||||||
|
"/":
|
||||||
|
port: 9000
|
||||||
|
|
||||||
|
virtualpath:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "10001"
|
||||||
|
- "10002"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "10001 10002"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
virtualpaths.nginx-proxy.tld:
|
||||||
|
"/rootdest":
|
||||||
|
port: 10001
|
||||||
|
dest: "/"
|
||||||
|
"/customdest":
|
||||||
|
port: 10002
|
||||||
|
dest: "/port"
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
18
test/test_multiports/test_multiports-invalid-syntax.py
Normal file
18
test/test_multiports/test_multiports-invalid-syntax.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import pytest
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def test_virtual_hosts_with_syntax_error_should_not_be_reachable(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://test1.nginx-proxy.tld")
|
||||||
|
assert r.status_code == 503
|
||||||
|
r = nginxproxy.get("http://test2.nginx-proxy.tld")
|
||||||
|
assert r.status_code == 503
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_should_have_multiports_warning_comments(docker_compose, nginxproxy):
|
||||||
|
conf = nginxproxy.get_conf().decode('ASCII')
|
||||||
|
matches = re.findall(r"the VIRTUAL_HOST_MULTIPORTS environment variable used for this container is not a valid YAML string", conf)
|
||||||
|
assert len(matches) == 3
|
||||||
|
assert "# invalidsyntax" in conf
|
||||||
|
assert "# hostnamerepeat" in conf
|
||||||
|
assert "# pathrepeat" in conf
|
44
test/test_multiports/test_multiports-invalid-syntax.yml
Normal file
44
test/test_multiports/test_multiports-invalid-syntax.yml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
version: "2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
invalidsyntax:
|
||||||
|
image: web
|
||||||
|
container_name: invalidsyntax
|
||||||
|
expose:
|
||||||
|
- "80"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "80"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
test1.nginx-proxy.tld
|
||||||
|
test2.nginx-proxy.tld:
|
||||||
|
|
||||||
|
hostnamerepeat:
|
||||||
|
image: web
|
||||||
|
container_name: hostnamerepeat
|
||||||
|
expose:
|
||||||
|
- "80"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "80"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
test1.nginx-proxy.tld:
|
||||||
|
test1.nginx-proxy.tld:
|
||||||
|
|
||||||
|
pathrepeat:
|
||||||
|
image: web
|
||||||
|
container_name: pathrepeat
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
|
- "9000"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "8080 9000"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
test1.nginx-proxy.tld:
|
||||||
|
"/":
|
||||||
|
port: 8080
|
||||||
|
"/":
|
||||||
|
port: 9000
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
14
test/test_multiports/test_multiports-merge.py
Normal file
14
test/test_multiports/test_multiports-merge.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import backoff
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiports_and_legacy_configs_should_be_merged(docker_compose, nginxproxy):
|
||||||
|
@backoff.on_predicate(backoff.constant, lambda r: r == False, interval=.5, max_tries=20, jitter=None)
|
||||||
|
def answer_contains(answer, url):
|
||||||
|
return answer in nginxproxy.get(url).text
|
||||||
|
|
||||||
|
assert answer_contains("80", "http://merged.nginx-proxy.tld/port")
|
||||||
|
assert answer_contains("81", "http://merged.nginx-proxy.tld/port")
|
||||||
|
|
||||||
|
assert answer_contains("9090", "http://merged.nginx-proxy.tld/foo/port")
|
||||||
|
assert answer_contains("9191", "http://merged.nginx-proxy.tld/foo/port")
|
41
test/test_multiports/test_multiports-merge.yml
Normal file
41
test/test_multiports/test_multiports-merge.yml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
version: "2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
merged-singleport:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "80"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "80"
|
||||||
|
VIRTUAL_HOST: merged.nginx-proxy.tld
|
||||||
|
|
||||||
|
merged-singleport-virtual-path:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "9090"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "9090"
|
||||||
|
VIRTUAL_HOST: merged.nginx-proxy.tld
|
||||||
|
VIRTUAL_PORT: "9090"
|
||||||
|
VIRTUAL_PATH: "/foo"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
merged-multiports:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
- "9191"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81 9191"
|
||||||
|
VIRTUAL_HOST_MULTIPORTS: |-
|
||||||
|
merged.nginx-proxy.tld:
|
||||||
|
"/":
|
||||||
|
port: 81
|
||||||
|
"/foo":
|
||||||
|
port: 9191
|
||||||
|
dest: "/"
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
Loading…
x
Reference in New Issue
Block a user