Path-based routing

This commit is contained in:
Josh Trow 2017-12-25 10:12:40 -05:00
commit 3b71bf0a9f
2 changed files with 106 additions and 85 deletions

View file

@ -81,6 +81,12 @@ If you need to support multiple virtual hosts for a container, you can separate
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 [xip.io](http://xip.io), using `~^foo\.bar\..*\.xip\.io` will match `foo.bar.127.0.0.1.xip.io`, `foo.bar.10.0.2.2.xip.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). 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 [xip.io](http://xip.io), using `~^foo\.bar\..*\.xip\.io` will match `foo.bar.127.0.0.1.xip.io`, `foo.bar.10.0.2.2.xip.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).
### Path-based Routing
You can have multiple containers proxied by the same `VIRTUAL_HOST` by adding a `VIRTUAL_PATH` environment variable containing the absolute path to where the container should be mounted. For example with `VIRTUAL_HOST=foo.example.com` and `VIRTUAL_PATH=/api/v2/service`, then requests to http://foo.example.com/api/v2/service will be routed to the container. If you wish to have a container serve the root while other containers serve other paths, make give the root container a `VIRTUAL_PATH` of `/`. Unmatched paths will be served by the container at `/` or will return the default nginx error page if no container has been assigned `/`.
The full request URI will be forwarded to the serving container in the `X-Forwarded-Path` header.
### Multiple Networks ### Multiple Networks
With the addition of [overlay networking](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) in Docker 1.9, your `nginx-proxy` container may need to connect to backend containers on multiple networks. By default, if you don't pass the `--net` flag when your `nginx-proxy` container is created, it will only be attached to the default `bridge` network. This means that it will not be able to connect to containers on networks other than `bridge`. With the addition of [overlay networking](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) in Docker 1.9, your `nginx-proxy` container may need to connect to backend containers on multiple networks. By default, if you don't pass the `--net` flag when your `nginx-proxy` container is created, it will only be attached to the default `bridge` network. This means that it will not be able to connect to containers on networks other than `bridge`.
@ -127,11 +133,11 @@ backend container. Your backend container should then listen on a port rather
than a socket and expose that port. than a socket and expose that port.
### FastCGI Backends ### FastCGI Backends
If you would like to connect to FastCGI backend, set `VIRTUAL_PROTO=fastcgi` on the If you would like to connect to FastCGI backend, set `VIRTUAL_PROTO=fastcgi` on the
backend container. Your backend container should then listen on a port rather backend container. Your backend container should then listen on a port rather
than a socket and expose that port. than a socket and expose that port.
### FastCGI Filr Root Directory ### FastCGI Filr Root Directory
If you use fastcgi,you can set `VIRTUAL_ROOT=xxx` for your root directory If you use fastcgi,you can set `VIRTUAL_ROOT=xxx` for your root directory
@ -180,7 +186,7 @@ Finally, start your containers with `VIRTUAL_HOST` environment variables.
$ docker run -e VIRTUAL_HOST=foo.bar.com ... $ docker run -e VIRTUAL_HOST=foo.bar.com ...
### SSL Support using letsencrypt ### SSL Support using letsencrypt
[letsencrypt-nginx-proxy-companion](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion) is a lightweight companion container for the nginx-proxy. It allow the creation/renewal of Let's Encrypt certificates automatically. [letsencrypt-nginx-proxy-companion](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion) is a lightweight companion container for the nginx-proxy. It allow the creation/renewal of Let's Encrypt certificates automatically.
### SSL Support ### SSL Support
@ -213,7 +219,7 @@ at startup. Since it can take minutes to generate a new `dhparam.pem`, it is do
background. Once generation is complete, the `dhparam.pem` is saved on a persistent volume and nginx background. Once generation is complete, the `dhparam.pem` is saved on a persistent volume and nginx
is reloaded. This generation process only occurs the first time you start `nginx-proxy`. is reloaded. This generation process only occurs the first time you start `nginx-proxy`.
> COMPATIBILITY WARNING: The default generated `dhparam.pem` key is 2048 bits for A+ security. Some > COMPATIBILITY WARNING: The default generated `dhparam.pem` key is 2048 bits for A+ security. Some
> older clients (like Java 6 and 7) do not support DH keys with over 1024 bits. In order to support these > older clients (like Java 6 and 7) do not support DH keys with over 1024 bits. In order to support these
> clients, you must either provide your own `dhparam.pem`, or tell `nginx-proxy` to generate a 1024-bit > clients, you must either provide your own `dhparam.pem`, or tell `nginx-proxy` to generate a 1024-bit
> key on startup by passing `-e DHPARAM_BITS=1024`. > key on startup by passing `-e DHPARAM_BITS=1024`.
@ -272,19 +278,19 @@ a 500.
To serve traffic in both SSL and non-SSL modes without redirecting to SSL, you can include the To serve traffic in both SSL and non-SSL modes without redirecting to SSL, you can include the
environment variable `HTTPS_METHOD=noredirect` (the default is `HTTPS_METHOD=redirect`). You can also environment variable `HTTPS_METHOD=noredirect` (the default is `HTTPS_METHOD=redirect`). You can also
disable the non-SSL site entirely with `HTTPS_METHOD=nohttp`, or disable the HTTPS site with disable the non-SSL site entirely with `HTTPS_METHOD=nohttp`, or disable the HTTPS site with
`HTTPS_METHOD=nohttps`. `HTTPS_METHOD` must be specified on each container for which you want to `HTTPS_METHOD=nohttps`. `HTTPS_METHOD` must be specified on each container for which you want to
override the default behavior. If `HTTPS_METHOD=noredirect` is used, Strict Transport Security (HSTS) override the default behavior. If `HTTPS_METHOD=noredirect` is used, Strict Transport Security (HSTS)
is disabled to prevent HTTPS users from being redirected by the client. If you cannot get to the HTTP is disabled to prevent HTTPS users from being redirected by the client. If you cannot get to the HTTP
site after changing this setting, your browser has probably cached the HSTS policy and is automatically site after changing this setting, your browser has probably cached the HSTS policy and is automatically
redirecting you back to HTTPS. You will need to clear your browser's HSTS cache or use an incognito redirecting you back to HTTPS. You will need to clear your browser's HSTS cache or use an incognito
window / different browser. window / different browser.
By default, [HTTP Strict Transport Security (HSTS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) By default, [HTTP Strict Transport Security (HSTS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)
is enabled with `max-age=31536000` for HTTPS sites. You can disable HSTS with the environment variable is enabled with `max-age=31536000` for HTTPS sites. You can disable HSTS with the environment variable
`HSTS=off` or use a custom HSTS configuration like `HSTS=max-age=31536000; includeSubDomains; preload`. `HSTS=off` or use a custom HSTS configuration like `HSTS=max-age=31536000; includeSubDomains; preload`.
*WARNING*: HSTS will force your users to visit the HTTPS version of your site for the `max-age` time - *WARNING*: HSTS will force your users to visit the HTTPS version of your site for the `max-age` time -
even if they type in `http://` manually. The only way to get to an HTTP site after receiving an HSTS even if they type in `http://` manually. The only way to get to an HTTP site after receiving an HSTS
response is to clear your browser's HSTS cache. response is to clear your browser's HSTS cache.
### Basic Authentication Support ### Basic Authentication Support
@ -323,6 +329,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Forwarded-Path $request_uri;
# Mitigate httpoxy attack (see README for details) # Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy ""; proxy_set_header Proxy "";
@ -400,7 +407,7 @@ Before submitting pull requests or issues, please check github to make sure an e
To run tests, you need to prepare the docker image to test which must be tagged `jwilder/nginx-proxy:test`: To run tests, you need to prepare the docker image to test which must be tagged `jwilder/nginx-proxy:test`:
docker build -t jwilder/nginx-proxy:test . # build the Debian variant image docker build -t jwilder/nginx-proxy:test . # build the Debian variant image
and call the [test/pytest.sh](test/pytest.sh) script. and call the [test/pytest.sh](test/pytest.sh) script.
Then build the Alpine variant of the image: Then build the Alpine variant of the image:
@ -413,6 +420,6 @@ and call the [test/pytest.sh](test/pytest.sh) script again.
If your system has the `make` command, you can automate those tasks by calling: If your system has the `make` command, you can automate those tasks by calling:
make test make test
You can learn more about how the test suite works and how to write new tests in the [test/README.md](test/README.md) file. You can learn more about how the test suite works and how to write new tests in the [test/README.md](test/README.md) file.

View file

@ -17,6 +17,59 @@
{{ end }} {{ end }}
{{ end }} {{ end }}
{{ define "location" }}
location {{ .Path }} {
{{ if eq .Proto "uwsgi" }}
include uwsgi_params;
uwsgi_pass {{ trim .Proto }}://{{ trim .Upstream }};
{{ else if eq (trim .Proto) "fastcgi" }}
root {{ trim .VHostRoot }};
include fastcgi.conf;
fastcgi_pass {{ trim .Upstream }};
{{ else }}
proxy_pass {{ trim .Proto }}://{{ trim .Upstream }}/;
{{ end }}
{{ if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }}
auth_basic "Restricted {{ .Host }}";
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" .Host) }};
{{ end }}
{{ if (exists (printf "/etc/nginx/vhost.d/%s_location" .Host)) }}
include {{ printf "/etc/nginx/vhost.d/%s_location" .Host}};
{{ else if (exists "/etc/nginx/vhost.d/default_location") }}
include /etc/nginx/vhost.d/default_location;
{{ end }}
}
{{ end }}
{{ define "upstream-definition" }}
upstream {{ .Upstream }} {
{{ range $container := .Containers }}
{{ $addrLen := len $container.Addresses }}
{{ $networks := .Networks }}
{{ range $knownNetwork := $networks }}
{{ range $containerNetwork := $container.Networks }}
{{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }}
## Can be connect with "{{ $containerNetwork.Name }}" network
{{/* If only 1 port exposed, use that */}}
{{ if eq $addrLen 1 }}
{{ $address := index $container.Addresses 0 }}
{{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }}
{{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var, falling back to standard web port 80 */}}
{{ else }}
{{ $port := coalesce $container.Env.VIRTUAL_PORT "80" }}
{{ $address := where $container.Addresses "Port" $port | first }}
{{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
}
{{ 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 {
@ -59,6 +112,7 @@ log_format vhost '$host $remote_addr - $remote_user [$time_local] '
'"$http_referer" "$http_user_agent"'; '"$http_referer" "$http_user_agent"';
access_log off; access_log off;
error_log /dev/stderr;
{{ if $.Env.RESOLVERS }} {{ if $.Env.RESOLVERS }}
resolver {{ $.Env.RESOLVERS }}; resolver {{ $.Env.RESOLVERS }};
@ -78,6 +132,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Forwarded-Path $request_uri;
# Mitigate httpoxy attack (see README for details) # Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy ""; proxy_set_header Proxy "";
@ -112,36 +167,20 @@ server {
{{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }} {{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }}
{{ $host := trim $host }} {{ $paths := groupBy $containers "Env.VIRTUAL_PATH" }}
{{ $is_regexp := hasPrefix "~" $host }} {{ $nPaths := len $paths }}
{{ $upstream_name := when $is_regexp (sha1 $host) $host }}
# {{ $host }} {{ if eq $nPaths 0 }}
upstream {{ $upstream_name }} { # {{ $host }}
{{ template "upstream-definition" (dict "Upstream" $host "Containers" $containers "Networks" (json $CurrentContainer.Networks)) }}
{{ range $container := $containers }} {{ else }}
{{ $addrLen := len $container.Addresses }} {{ range $path, $containers := $paths }}
{{ $sum := sha1 $path }}
{{ range $knownNetwork := $CurrentContainer.Networks }} {{ $upstream := printf "%s-%s" $host $sum }}
{{ range $containerNetwork := $container.Networks }} # {{ $host }}{{ $path }}
{{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }} {{ template "upstream-definition" (dict "Upstream" $upstream "Containers" $containers "Networks" $CurrentContainer.Networks) }}
## Can be connect with "{{ $containerNetwork.Name }}" network
{{/* If only 1 port exposed, use that */}}
{{ if eq $addrLen 1 }}
{{ $address := index $container.Addresses 0 }}
{{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }}
{{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var, falling back to standard web port 80 */}}
{{ else }}
{{ $port := coalesce $container.Env.VIRTUAL_PORT "80" }}
{{ $address := where $container.Addresses "Port" $port | first }}
{{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }}
{{ end }}
{{ end }}
{{ end }}
{{ end }} {{ end }}
{{ end }} {{ end }}
}
{{ $default_host := or ($.Env.DEFAULT_HOST) "" }} {{ $default_host := or ($.Env.DEFAULT_HOST) "" }}
{{ $default_server := index (dict $host "" $default_host "default_server") $host }} {{ $default_server := index (dict $host "" $default_host "default_server") $host }}
@ -267,28 +306,15 @@ server {
include /etc/nginx/vhost.d/default; include /etc/nginx/vhost.d/default;
{{ end }} {{ end }}
location / { {{ if eq $nPaths 0 }}
{{ if eq $proto "uwsgi" }} {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $host "Host" $host "VHostRoot" $vhost_root) }}
include uwsgi_params; {{ else }}
uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }}; {{ range $path, $containers := $paths }}
{{ else if eq $proto "fastcgi" }} {{ $sum := sha1 $path }}
root {{ trim $vhost_root }}; {{ $upstream := printf "%s-%s" $host $sum }}
include fastcgi.conf; {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VHostRoot" $vhost_root) }}
fastcgi_pass {{ trim $upstream_name }};
{{ else }}
proxy_pass {{ trim $proto }}://{{ trim $upstream_name }};
{{ end }} {{ end }}
{{ end }}
{{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
auth_basic "Restricted {{ $host }}";
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" $host) }};
{{ end }}
{{ if (exists (printf "/etc/nginx/vhost.d/%s_location" $host)) }}
include {{ printf "/etc/nginx/vhost.d/%s_location" $host}};
{{ else if (exists "/etc/nginx/vhost.d/default_location") }}
include /etc/nginx/vhost.d/default_location;
{{ end }}
}
} }
{{ end }} {{ end }}
@ -314,27 +340,15 @@ server {
include /etc/nginx/vhost.d/default; include /etc/nginx/vhost.d/default;
{{ end }} {{ end }}
location / { {{ if eq $nPaths 0 }}
{{ if eq $proto "uwsgi" }} {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $host "Host" $host "VHostRoot" $vhost_root) }}
include uwsgi_params; {{ else }}
uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }}; {{ range $path, $containers := $paths }}
{{ else if eq $proto "fastcgi" }} {{ $sum := sha1 $path }}
root {{ trim $vhost_root }}; {{ $upstream := printf "%s-%s" $host $sum }}
include fastcgi.conf; {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VHostRoot" $vhost_root) }}
fastcgi_pass {{ trim $upstream_name }};
{{ else }}
proxy_pass {{ trim $proto }}://{{ trim $upstream_name }};
{{ end }} {{ end }}
{{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }} {{ end }}
auth_basic "Restricted {{ $host }}";
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" $host) }};
{{ end }}
{{ if (exists (printf "/etc/nginx/vhost.d/%s_location" $host)) }}
include {{ printf "/etc/nginx/vhost.d/%s_location" $host}};
{{ else if (exists "/etc/nginx/vhost.d/default_location") }}
include /etc/nginx/vhost.d/default_location;
{{ end }}
}
} }
{{ if (and (not $is_https) (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} {{ if (and (not $is_https) (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }}