{{ define "http_default_host" }} server { {{ template "server_basic_settings" (dict "host" .host) }} return {{ coalesce .error_code "500" }}; } {{ end }} {{ define "https_default_host" }} {{ if and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key") }} server { {{ template "server_basic_settings" (dict "host" .host "secure" true "default_server" .default_server) }} {{ template "server_ssl_settings" (dict "cert" "default") }} return {{ coalesce .error_code "500" }}; } {{ end }} {{ end }} {{ define "server" }} {{ $me := .me }} {{ $host := .host }} {{ $port := .port }} {{ $port_containers := coalesce .port_containers dict }} {{ $root_container_map := dict "/" (whereNotExist $port_containers "Env.VIRTUAL_PATH") }} {{ $path_container_map := groupBy $port_containers "Env.VIRTUAL_PATH" }} {{ $is_default_port := eq $port "default" }} {{ $default_host := or $.Env.DEFAULT_HOST "" }} {{ $default_server := index (dict $host "" $default_host "default_server") $host }} {{/* Get the HTTPS_METHOD defined by containers w/ the same vhost, falling back to "redirect" */}} {{ $https_method := or (first (groupByKeys $port_containers "Env.HTTPS_METHOD")) "redirect" }} {{/* Get the first cert name defined by containers w/ the same vhost */}} {{ $certName := first (groupByKeys $port_containers "Env.CERT_NAME") }} {{/* Get the best matching cert file by name for the vhost and remove all suffixes. */}} {{ $vhostCert := trimSuffix ".key" (trimSuffix ".crt" (closest (dir "/etc/nginx/certs") (printf "%s.crt" $host))) }} {{/* Use the cert specified on the container or fallback to the best vhost match */}} {{ $cert := coalesce $certName $vhostCert }} {{ $with_https := and (ne $https_method "nohttps") (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert)) }} {{ template "upstream" (dict "me" $me "host" $host "port" $port "path_container_map" $root_container_map) }} {{ template "upstream" (dict "me" $me "host" $host "port" $port "path_container_map" $path_container_map) }} {{ if $with_https }} server { {{ template "server_basic_settings" (dict "host" $host "port" $port "secure" true "default_server" $default_server) }} {{ template "server_ssl_settings" (dict "cert" $cert "strong_encryption" true) }} {{ template "server_settings" (dict "host" $host "port" $port) }} {{ template "server_locations" (dict "host" $host "port" $port "path_container_map" $root_container_map) }} {{ template "server_locations" (dict "host" $host "port" $port "path_container_map" $path_container_map) }} {{ if ne $https_method "noredirect" }} add_header Strict-Transport-Security "max-age=31536000"; {{ end }} } {{ end }} {{ if or (not $with_https) (and $is_default_port (eq $https_method "noredirect")) }} server { {{ template "server_basic_settings" (dict "host" $host "port" $port "default_server" $default_server) }} {{ template "server_settings" (dict "host" $host "port" $port) }} {{ template "server_locations" (dict "host" $host "port" $port "path_container_map" $root_container_map) }} {{ template "server_locations" (dict "host" $host "port" $port "path_container_map" $path_container_map) }} } {{ else if and $with_https $is_default_port (eq $https_method "redirect") }} server { {{ template "server_basic_settings" (dict "host" $host "default_server" $default_server) }} return 301 https://$host$request_uri; } {{ end }} {{ end }} {{ define "server_basic_settings" }} {{ $port := coalesce .port "default" }} {{ $is_https := coalesce .secure (eq $port "443") }} {{ $default_server := coalesce .default_server "" }} server_name {{ .host }}; {{ if $is_https }} listen {{ when (eq $port "default") "443" $port }} ssl http2 {{- $default_server }}; {{ else }} listen {{ when (eq $port "default") "80" $port }} {{- $default_server }}; {{ end }} access_log /var/log/nginx/access.log vhost; {{ end }} {{ define "server_ssl_settings" }} {{ if .strong_encryption }} ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; ssl_prefer_server_ciphers on; ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; {{ end }} ssl_session_tickets off; ssl_certificate {{ printf "/etc/nginx/certs/%s.crt" .cert }}; ssl_certificate_key {{ printf "/etc/nginx/certs/%s.key" .cert }}; {{ if exists (printf "/etc/nginx/certs/%s.dhparam.pem" .cert) }} ssl_dhparam {{ printf "/etc/nginx/certs/%s.dhparam.pem" .cert }}; {{ end }} {{ end }} {{ define "server_settings" }} {{ $host := .host }} {{ $port := .port }} {{ if exists (printf "/etc/nginx/vhost.d/%s_%s" $host $port) }} include {{ printf "/etc/nginx/vhost.d/%s_%s" $host $port }}; {{ else if exists (printf "/etc/nginx/vhost.d/%s" $host) }} include {{ printf "/etc/nginx/vhost.d/%s" $host }}; {{ else if exists "/etc/nginx/vhost.d/default" }} include /etc/nginx/vhost.d/default; {{ end }} {{ end }} {{ define "server_locations" }} {{ $host := .host }} {{ $port := .port }} {{ $path_container_map := .path_container_map }} {{ range $path, $path_containers := $path_container_map }} {{ $path := trimSuffix "/" $path }} {{ $slug := replace $path "/" "_" -1 }} {{ if gt (len $path_containers) 0 }} location {{ $path }}/ { {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}} {{ $proto := or (first (groupByKeys $path_containers "Env.VIRTUAL_BACKEND_PROTO")) "http" }} {{ $upstream_name := printf "%s_%s%s" $host $port $slug }} {{ $path_pass_through := parseBool (or (first (groupByKeys $path_containers "Env.VIRTUAL_PATH_PASS_THROUGH")) "false") }} {{/* For an explanation of the optional slash, see http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass */}} {{ $backend_url := printf "%s://%s%s" $proto $upstream_name (when $path_pass_through "" "/") }} {{ if eq $proto "uwsgi" }} include uwsgi_params; uwsgi_pass {{ $backend_url }}; {{ else }} proxy_pass {{ $backend_url }}; {{ end }} {{ if exists (printf "/etc/nginx/basic_auth/%s_%s_%s/htpasswd" $host $port $slug) }} auth_basic "Restricted {{ $host }}"; auth_basic_user_file {{ printf "/etc/nginx/basic_auth/%s_%s_%s/htpasswd" $host $port $slug }}; {{ else if exists (printf "/etc/nginx/basic_auth/%s_%s/htpasswd" $host $port) }} auth_basic "Restricted {{ $host }}"; auth_basic_user_file {{ printf "/etc/nginx/basic_auth/%s_%s/htpasswd" $host $port }}; {{ else if exists (printf "/etc/nginx/basic_auth/%s/htpasswd" $host) }} auth_basic "Restricted {{ $host }}"; auth_basic_user_file {{ printf "/etc/nginx/basic_auth/%s/htpasswd" $host }}; {{ end }} {{ if exists (printf "/etc/nginx/vhost.d/%s_%s_%s_location" $host $port $slug) }} include {{ printf "/etc/nginx/vhost.d/%s_%s_%s_location" $host $port $slug }}; {{ else if exists (printf "/etc/nginx/vhost.d/%s_%s_location" $host $port) }} include {{ printf "/etc/nginx/vhost.d/%s_%s_location" $host $port }}; {{ else 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 }} {{ end }} {{ define "upstream_server" }} ## Container connected via network "{{ .Network.Name }}" {{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}} {{ if and .Address .Container.Node.ID .Address.HostPort }} ## {{ .Container.Node.Name }}/{{ .Container.Name }} server {{ .Container.Node.Address.IP }}:{{ .Address.HostPort }}; {{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}} {{ else if and .Address .Network }} ## {{ .Container.Name }} server {{ .Network.IP }}:{{ .Address.Port }}; {{ else if .Network }} ## {{ .Container.Name }} server {{ .Network.IP }} down; {{ end }} {{ end }} {{ define "upstream_switch" }} {{ if eq (len .Container.Addresses) 1 }} {{/* If only 1 port exposed, use that */}} {{ $address := index .Container.Addresses 0 }} {{ template "upstream_server" (dict "Container" .Container "Address" $address "Network" .Network) }} {{ else }} {{/* if more than one port exposed, use the one matching VIRTUAL_BACKEND_PORT env var, falling back to standard web port 80 */}} {{ $address := first (where .Container.Addresses "Port" (coalesce .Container.Env.VIRTUAL_BACKEND_PORT "80")) }} {{ template "upstream_server" (dict "Container" .Container "Address" $address "Network" .Network) }} {{ end }} {{ end }} {{ define "upstream" }} {{ $me := .me }} {{ $host := .host }} {{ $port := .port }} {{ $path_container_map := .path_container_map}} {{ range $path, $path_containers := $path_container_map }} {{ if gt (len $path_containers) 0 }} {{ $upstream_name := printf "%s_%s%s" $host $port (trimSuffix "_" (replace $path "/" "_" -1)) }} upstream {{ $upstream_name }} { {{ range $container := $path_containers }} {{ range $knownNetwork := $me.Networks }} {{ range $containerNetwork := $container.Networks }} {{ if eq $knownNetwork.Name $containerNetwork.Name }} {{ template "upstream_switch" (dict "Container" $container "Network" $containerNetwork) }} {{ end }} {{ end }} {{ end }} {{ end }} {{/* This avoids invalid upstream configuration in case something breaks */}} server 127.0.0.1 down; } {{ end }} {{ end }} {{ end }} ## If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the scheme used to connect to this server map $http_x_forwarded_proto $proxy_x_forwarded_proto { default $http_x_forwarded_proto; '' $scheme; } ## If we receive X-Forwarded-Port, pass it through; otherwise, pass along the server port the client connected to map $http_x_forwarded_port $proxy_x_forwarded_port { default $http_x_forwarded_port; '' $server_port; } ## If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any Connection header that may have been passed to this server map $http_upgrade $proxy_connection { default upgrade; '' close; } ## Set appropriate X-Forwarded-Ssl header map $scheme $proxy_x_forwarded_ssl { default off; https on; } {{ if exists "/etc/nginx/proxy.conf" }} include /etc/nginx/proxy.conf; {{ else }} ## HTTP 1.1 support proxy_http_version 1.1; proxy_buffering off; proxy_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $proxy_connection; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; ## Mitigate httpoxy attack (see README for details) proxy_set_header Proxy ""; {{ end }} gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; log_format vhost '$host $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"'; access_log off; {{ $me := where $ "ID" .Docker.CurrentContainerID | first }} {{/* First host definitions will catch access to the IP or to invalid hostnames */}} {{ template "http_default_host" (dict "host" "_" "error_code" 503) }} {{ template "https_default_host" (dict "host" "_" "error_code" 503) }} {{ range $host, $host_containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }} {{ $def_port_container_map := dict "default" (whereNotExist $host_containers "Env.VIRTUAL_PORT") }} {{ $port_container_map := groupBy $host_containers "Env.VIRTUAL_PORT" }} {{ range $port, $port_containers := $def_port_container_map }} {{ template "server" (dict "me" $me "host" $host "port" $port "port_containers" $port_containers )}} {{ end }} {{ range $port, $port_containers := $port_container_map }} {{ template "server" (dict "me" $me "host" $host "port" $port "port_containers" $port_containers )}} {{ end }} {{ end }}