Browse Source

Merge pull request #2279 from Knapoc/network-segregation-seperate-containers

feat: allow nginx / docker-gen network segregation
Nicolas Duchon 3 weeks ago
parent
commit
145278b0ae

+ 47 - 7
docs/README.md

@@ -1136,25 +1136,65 @@ I'm 5b129ab83266
 
 
 To run nginx proxy as a separate container you'll need to have [nginx.tmpl](https://github.com/nginx-proxy/nginx-proxy/blob/main/nginx.tmpl) on your host system.
 To run nginx proxy as a separate container you'll need to have [nginx.tmpl](https://github.com/nginx-proxy/nginx-proxy/blob/main/nginx.tmpl) on your host system.
 
 
-First start nginx with a volume:
+First start nginx with a volume mounted to `/etc/nginx/conf.d`:
 
 
 ```console
 ```console
-docker run -d -p 80:80 --name nginx -v /tmp/nginx:/etc/nginx/conf.d -t nginx
+docker run --detach \
+    --name nginx \
+    --publish 80:80 \
+    --volume /tmp/nginx:/etc/nginx/conf.d \
+    nginx
 ```
 ```
 
 
 Then start the docker-gen container with the shared volume and template:
 Then start the docker-gen container with the shared volume and template:
 
 
 ```console
 ```console
-docker run --volumes-from nginx \
-    -v /var/run/docker.sock:/tmp/docker.sock:ro \
-    -v $(pwd):/etc/docker-gen/templates \
-    -t nginxproxy/docker-gen -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
+docker run --detach \
+    --name docker-gen \
+    --volumes-from nginx \
+    --volume /var/run/docker.sock:/tmp/docker.sock:ro \
+    --volume $(pwd):/etc/docker-gen/templates \
+    nginxproxy/docker-gen -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
 ```
 ```
 
 
 Finally, start your containers with `VIRTUAL_HOST` environment variables.
 Finally, start your containers with `VIRTUAL_HOST` environment variables.
 
 
 ```console
 ```console
-docker run -e VIRTUAL_HOST=foo.bar.com  ...
+docker run --env VIRTUAL_HOST=foo.bar.com  ...
+```
+
+### Network segregation
+
+To allow for network segregation of the nginx and docker-gen containers, the label `com.github.nginx-proxy.nginx-proxy.nginx` must be applied to the nginx container, otherwise it is assumed that nginx and docker-gen share the same network:
+
+```console
+docker run --detach \
+    --name nginx \
+    --publish 80:80 \
+    --label "com.github.nginx-proxy.nginx-proxy.nginx" \
+    --volume /tmp/nginx:/etc/nginx/conf.d \
+    nginx
+```
+
+Network segregation make it possible to run the docker-gen container in an [internal network](https://docs.docker.com/reference/cli/docker/network/create/#internal), unreachable from the outside.
+
+You can also customise the label being used by docker-gen to find the nginx container with the `NGINX_CONTAINER_LABEL`environment variable (on the docker-gen container):
+
+```console
+docker run --detach \
+    --name docker-gen \
+    --volumes-from nginx \
+    --volume /var/run/docker.sock:/tmp/docker.sock:ro \
+    --volume $(pwd):/etc/docker-gen/templates \
+    --env "NGINX_CONTAINER_LABEL=com.github.foobarbuzz" \
+    nginxproxy/docker-gen -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
+
+docker run --detach \
+    --name nginx \
+    --publish 80:80 \
+    --label "com.github.foobarbuzz" \
+    --volume "/tmp/nginx:/etc/nginx/conf.d" \
+    nginx
 ```
 ```
 
 
 ⬆️ [back to table of contents](#table-of-contents)
 ⬆️ [back to table of contents](#table-of-contents)

+ 28 - 25
nginx.tmpl

@@ -10,10 +10,10 @@
 {{- $_ := set $globals "containers" $ }}
 {{- $_ := set $globals "containers" $ }}
 {{- $_ := set $globals "Env" $.Env }}
 {{- $_ := set $globals "Env" $.Env }}
 {{- $_ := set $globals "Docker" $.Docker }}
 {{- $_ := set $globals "Docker" $.Docker }}
-{{- $_ := set $globals "CurrentContainer" (where $globals.containers "ID" $globals.Docker.CurrentContainerID | first) }}
 
 
 {{- $config := dict }}
 {{- $config := dict }}
 {{- $_ := set $config "nginx_proxy_version" $.Env.NGINX_PROXY_VERSION }}
 {{- $_ := set $config "nginx_proxy_version" $.Env.NGINX_PROXY_VERSION }}
+{{- $_ := set $config "nginx_container_label" ($.Env.NGINX_CONTAINER_LABEL | default "com.github.nginx-proxy.nginx-proxy.nginx") }}
 {{- $_ := set $config "default_cert_ok" (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }}
 {{- $_ := set $config "default_cert_ok" (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }}
 {{- $_ := set $config "external_http_port" ($globals.Env.HTTP_PORT | default "80") }}
 {{- $_ := set $config "external_http_port" ($globals.Env.HTTP_PORT | default "80") }}
 {{- $_ := set $config "external_https_port" ($globals.Env.HTTPS_PORT | default "443") }}
 {{- $_ := set $config "external_https_port" ($globals.Env.HTTPS_PORT | default "443") }}
@@ -44,26 +44,29 @@
 
 
 {{- $_ := set $globals "vhosts" (dict) }}
 {{- $_ := set $globals "vhosts" (dict) }}
 {{- $_ := set $globals "networks" (dict) }}
 {{- $_ := set $globals "networks" (dict) }}
-# Networks available to the container running docker-gen (which are assumed to
-# match the networks available to the container running nginx):
+
+{{- $currentContainer := where $globals.containers "ID" $globals.Docker.CurrentContainerID | first }}
+{{- $labeledContainer := whereLabelExists $globals.containers $globals.config.nginx_container_label | first }}
+{{- $_ := set $globals "NetworkContainer" ($labeledContainer | default $currentContainer) }}
+# Networks available to the container labeled "{{ $globals.config.nginx_container_label }}" or the one running docker-gen
+# (which are assumed to match the networks available to the container running nginx):
 {{- /*
 {{- /*
-     * Note: $globals.CurrentContainer may be nil in some circumstances due to
-     * <https://github.com/nginx-proxy/docker-gen/issues/458>.  For more context
-     * see <https://github.com/nginx-proxy/nginx-proxy/issues/2189>.
+     * Note:
+     * $globals.NetworkContainer may be nil in some circumstances due to https://github.com/nginx-proxy/docker-gen/issues/458.
+     * For more context see https://github.com/nginx-proxy/nginx-proxy/issues/2189.
      */}}
      */}}
-{{- if $globals.CurrentContainer }}
-    {{- range sortObjectsByKeysAsc $globals.CurrentContainer.Networks "Name" }}
+{{- if $globals.NetworkContainer }}
+    {{- range sortObjectsByKeysAsc $globals.NetworkContainer.Networks "Name" }}
         {{- $_ := set $globals.networks .Name . }}
         {{- $_ := set $globals.networks .Name . }}
 #     {{ .Name }}
 #     {{ .Name }}
     {{- else }}
     {{- else }}
 #     (none)
 #     (none)
     {{- end }}
     {{- end }}
 {{- else }}
 {{- else }}
-# /!\ WARNING: Failed to find the Docker container running docker-gen.  All
-#              upstream (backend) application containers will appear to be
-#              unreachable.  Try removing the -only-exposed and -only-published
-#              arguments to docker-gen if you pass either of those.  See
-#              <https://github.com/nginx-proxy/docker-gen/issues/458>.
+# /!\ WARNING: Failed to find the Docker container labeled "{{ $globals.config.nginx_container_label }}" or the one running docker-gen. 
+#              All upstream (backend) application containers will appear to be unreachable.
+#              Try removing the -only-exposed and -only-published arguments to docker-gen if you pass either of those. 
+#              See https://github.com/nginx-proxy/docker-gen/issues/458.
 {{- end }}
 {{- end }}
 
 
 {{- /*
 {{- /*
@@ -97,7 +100,7 @@
                 {{- $ipv4 = "127.0.0.1" }}
                 {{- $ipv4 = "127.0.0.1" }}
                 {{- continue }}
                 {{- continue }}
             {{- end }}
             {{- end }}
-            {{- range sortObjectsByKeysAsc $.globals.CurrentContainer.Networks "Name" }}
+            {{- range sortObjectsByKeysAsc $.globals.NetworkContainer.Networks "Name" }}
                 {{- if and . .Gateway (not .Internal) }}
                 {{- if and . .Gateway (not .Internal) }}
     #         container is in host network mode, using {{ .Name }} gateway IP
     #         container is in host network mode, using {{ .Name }} gateway IP
                     {{- $ipv4 = .Gateway }}
                     {{- $ipv4 = .Gateway }}
@@ -114,7 +117,7 @@
         {{- end }}
         {{- end }}
         {{- /*
         {{- /*
              * Do not emit multiple `server` directives for this container if it
              * Do not emit multiple `server` directives for this container if it
-             * is reachable over multiple networks or multiple IP stacks. This avoids 
+             * is reachable over multiple networks or multiple IP stacks. This avoids
              * accidentally inflating the effective round-robin weight of a server due
              * accidentally inflating the effective round-robin weight of a server due
              * to the redundant upstream addresses that nginx sees as belonging to
              * to the redundant upstream addresses that nginx sees as belonging to
              * distinct servers.
              * distinct servers.
@@ -397,7 +400,7 @@ upstream {{ $vpath.upstream }} {
         {{- $debug_vpath := deepCopy $vpath | merge (dict "ports" $tmp_ports) }}
         {{- $debug_vpath := deepCopy $vpath | merge (dict "ports" $tmp_ports) }}
         {{- $_ := set $debug_paths $path $debug_vpath }}
         {{- $_ := set $debug_paths $path $debug_vpath }}
     {{- end }}
     {{- end }}
-    
+
     {{- $debug_vhost := deepCopy .VHost }}
     {{- $debug_vhost := deepCopy .VHost }}
     {{- /* If it's a regexp, do not render the Hostname to the response to avoid rendering config breaking characters */}}
     {{- /* If it's a regexp, do not render the Hostname to the response to avoid rendering config breaking characters */}}
     {{- $_ := set $debug_vhost "hostname" (.VHost.is_regexp | ternary "Hostname is a regexp and unsafe to include in the debug response." .Hostname) }}
     {{- $_ := set $debug_vhost "hostname" (.VHost.is_regexp | ternary "Hostname is a regexp and unsafe to include in the debug response." .Hostname) }}
@@ -606,7 +609,7 @@ proxy_set_header Proxy "";
             {{- $path_port_containers := get $path_ports $port | default (list) | concat $containers }}
             {{- $path_port_containers := get $path_ports $port | default (list) | concat $containers }}
             {{- $_ := set $path_ports $port $path_port_containers }}
             {{- $_ := set $path_ports $port $path_port_containers }}
             {{- $_ := set $path_data "ports" $path_ports }}
             {{- $_ := set $path_data "ports" $path_ports }}
-            
+
             {{- if (not (hasKey $path_data "dest")) }}
             {{- if (not (hasKey $path_data "dest")) }}
                 {{- $_ := set $path_data "dest" $dest }}
                 {{- $_ := set $path_data "dest" $dest }}
             {{- end }}
             {{- end }}
@@ -614,7 +617,7 @@ proxy_set_header Proxy "";
             {{- if (not (hasKey $path_data "proto")) }}
             {{- if (not (hasKey $path_data "proto")) }}
                 {{- $_ := set $path_data "proto" $proto }}
                 {{- $_ := set $path_data "proto" $proto }}
             {{- end }}
             {{- end }}
-            
+
             {{- $_ := set $paths $path $path_data }}
             {{- $_ := set $paths $path $path_data }}
         {{- end }}
         {{- end }}
         {{- $_ := set $vhost_data "paths" $paths }}
         {{- $_ := set $vhost_data "paths" $paths }}
@@ -666,7 +669,7 @@ proxy_set_header Proxy "";
         {{- if (not (hasKey $path_data "proto")) }}
         {{- if (not (hasKey $path_data "proto")) }}
             {{- $_ := set $path_data "proto" $proto }}
             {{- $_ := set $path_data "proto" $proto }}
         {{- end }}
         {{- end }}
-        
+
         {{- $_ := set $paths $path $path_data }}
         {{- $_ := set $paths $path $path_data }}
     {{- end }}
     {{- end }}
     {{- $_ := set $vhost_data "paths" $paths }}
     {{- $_ := set $vhost_data "paths" $paths }}
@@ -708,7 +711,7 @@ proxy_set_header Proxy "";
     {{- end }}
     {{- end }}
 
 
     {{- $userIdentifiedCert := groupByKeys $vhost_containers "Env.CERT_NAME" | first }}
     {{- $userIdentifiedCert := groupByKeys $vhost_containers "Env.CERT_NAME" | first }}
-    
+
     {{- $vhostCert := "" }}
     {{- $vhostCert := "" }}
     {{- if exists (printf "/etc/nginx/certs/%s.crt" $hostname) }}
     {{- if exists (printf "/etc/nginx/certs/%s.crt" $hostname) }}
         {{- $vhostCert = $hostname }}
         {{- $vhostCert = $hostname }}
@@ -721,10 +724,10 @@ proxy_set_header Proxy "";
             {{- $parentVhostCert = $parentHostname }}
             {{- $parentVhostCert = $parentHostname }}
         {{- end }}
         {{- end }}
     {{- end }}
     {{- end }}
-    
+
     {{- $trust_default_cert := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.trust-default-cert" | keys | first | default $globals.config.trust_default_cert | parseBool }}
     {{- $trust_default_cert := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.trust-default-cert" | keys | first | default $globals.config.trust_default_cert | parseBool }}
     {{- $defaultCert := and $trust_default_cert $globals.config.default_cert_ok | ternary "default" "" }}
     {{- $defaultCert := and $trust_default_cert $globals.config.default_cert_ok | ternary "default" "" }}
-    
+
     {{- $cert := or $userIdentifiedCert $vhostCert $parentVhostCert $defaultCert }}
     {{- $cert := or $userIdentifiedCert $vhostCert $parentVhostCert $defaultCert }}
     {{- $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)) }}
 
 
@@ -738,10 +741,10 @@ proxy_set_header Proxy "";
         {{- $https_method = "noredirect" }}
         {{- $https_method = "noredirect" }}
     {{- end }}
     {{- end }}
     {{- $non_get_redirect := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.non-get-redirect" | keys | first | default $globals.config.non_get_redirect }}
     {{- $non_get_redirect := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.non-get-redirect" | keys | first | default $globals.config.non_get_redirect }}
-    
+
     {{- $http2_enabled := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http2.enable" | keys | first | default $globals.config.enable_http2 | parseBool }}
     {{- $http2_enabled := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http2.enable" | keys | first | default $globals.config.enable_http2 | parseBool }}
     {{- $http3_enabled := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http3.enable" | keys | first | default $globals.config.enable_http3 | parseBool }}
     {{- $http3_enabled := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http3.enable" | keys | first | default $globals.config.enable_http3 | parseBool }}
-    
+
     {{- $acme_http_challenge := groupByKeys $vhost_containers "Env.ACME_HTTP_CHALLENGE_LOCATION" | first | default $globals.config.acme_http_challenge }}
     {{- $acme_http_challenge := groupByKeys $vhost_containers "Env.ACME_HTTP_CHALLENGE_LOCATION" | first | default $globals.config.acme_http_challenge }}
     {{- $acme_http_challenge_legacy := eq $acme_http_challenge "legacy" }}
     {{- $acme_http_challenge_legacy := eq $acme_http_challenge "legacy" }}
     {{- $acme_http_challenge_enabled := false }}
     {{- $acme_http_challenge_enabled := false }}
@@ -903,7 +906,7 @@ server {
         break;
         break;
     }
     }
         {{- end }}
         {{- end }}
-    
+
         {{- if $vhost.enable_debug_endpoint }}
         {{- if $vhost.enable_debug_endpoint }}
             {{ template "debug_location" (dict "GlobalConfig" $globals.config "Hostname" $hostname "VHost" $vhost) }}
             {{ template "debug_location" (dict "GlobalConfig" $globals.config "Hostname" $hostname "VHost" $vhost) }}
         {{- end }}
         {{- end }}

+ 45 - 0
test/test_dockergen/test_dockergen_network_segregation-custom-label.base.yml

@@ -0,0 +1,45 @@
+networks:
+  proxy:
+  private:
+    internal: true
+
+volumes:
+  nginx_conf:
+
+
+services:
+  nginx-proxy-nginx:
+    image: nginx
+    container_name: nginx
+    volumes:
+      - nginx_conf:/etc/nginx/conf.d:ro
+    ports:
+      - "80:80"
+      - "443:443"
+    networks:
+      - proxy
+    labels:
+      - "com.github.nginx-proxy.nginx-proxy.foobarbuzz"
+
+  nginx-proxy-dockergen:
+    image: nginxproxy/docker-gen
+    command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
+    volumes:
+      - /var/run/docker.sock:/tmp/docker.sock:ro
+      - ../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
+      - nginx_conf:/etc/nginx/conf.d
+    environment:
+      NGINX_CONTAINER_LABEL: "com.github.nginx-proxy.nginx-proxy.foobarbuzz"
+    networks:
+      - private
+
+  web:
+    image: web
+    container_name: whoami2
+    expose:
+      - "80"
+    environment:
+      WEB_PORTS: "80"
+      VIRTUAL_HOST: whoami2.nginx.container.docker
+    networks:
+      - proxy

+ 27 - 0
test/test_dockergen/test_dockergen_network_segregation-custom-label.py

@@ -0,0 +1,27 @@
+import docker
+import pytest
+from packaging.version import Version
+
+
+raw_version = docker.from_env().version()["Version"]
+pytestmark = pytest.mark.skipif(
+    Version(raw_version) < Version("1.13"),
+    reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})"
+)
+
+
+def test_unknown_virtual_host_is_503(docker_compose, nginxproxy):
+    r = nginxproxy.get("http://unknown.nginx.container.docker/")
+    assert r.status_code == 503
+
+
+def test_forwards_to_whoami(docker_compose, nginxproxy):
+    r = nginxproxy.get("http://whoami2.nginx.container.docker/")
+    assert r.status_code == 200
+    whoami_container = docker_compose.containers.get("whoami2")
+    assert r.text == f"I'm {whoami_container.id[:12]}\n"
+
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()

+ 43 - 0
test/test_dockergen/test_dockergen_network_segregation.base.yml

@@ -0,0 +1,43 @@
+networks:
+  proxy:
+  private:
+    internal: true
+
+volumes:
+  nginx_conf:
+
+
+services:
+  nginx-proxy-nginx:
+    image: nginx
+    container_name: nginx
+    volumes:
+      - nginx_conf:/etc/nginx/conf.d:ro
+    ports:
+      - "80:80"
+      - "443:443"
+    networks:
+      - proxy
+    labels:
+      - "com.github.nginx-proxy.nginx-proxy.nginx"
+
+  nginx-proxy-dockergen:
+    image: nginxproxy/docker-gen
+    command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
+    volumes:
+      - /var/run/docker.sock:/tmp/docker.sock:ro
+      - ../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
+      - nginx_conf:/etc/nginx/conf.d
+    networks:
+      - private
+
+  web:
+    image: web
+    container_name: whoami2
+    expose:
+      - "80"
+    environment:
+      WEB_PORTS: "80"
+      VIRTUAL_HOST: whoami2.nginx.container.docker
+    networks:
+      - proxy

+ 27 - 0
test/test_dockergen/test_dockergen_network_segregation.py

@@ -0,0 +1,27 @@
+import docker
+import pytest
+from packaging.version import Version
+
+
+raw_version = docker.from_env().version()["Version"]
+pytestmark = pytest.mark.skipif(
+    Version(raw_version) < Version("1.13"),
+    reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})"
+)
+
+
+def test_unknown_virtual_host_is_503(docker_compose, nginxproxy):
+    r = nginxproxy.get("http://unknown.nginx.container.docker/")
+    assert r.status_code == 503
+
+
+def test_forwards_to_whoami(docker_compose, nginxproxy):
+    r = nginxproxy.get("http://whoami2.nginx.container.docker/")
+    assert r.status_code == 200
+    whoami_container = docker_compose.containers.get("whoami2")
+    assert r.text == f"I'm {whoami_container.id[:12]}\n"
+
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()