mirror of
				https://github.com/thib8956/nginx-proxy
				synced 2025-11-04 02:59:20 +00:00 
			
		
		
		
	Merge pull request #2499 from nginx-proxy/ipv6
feat: basic implementation of IPv6 for IPv6 docker networks
This commit is contained in:
		
							
								
								
									
										9
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							@@ -36,6 +36,15 @@ jobs:
 | 
			
		||||
          pip install -r python-requirements.txt
 | 
			
		||||
        working-directory: test/requirements
 | 
			
		||||
 | 
			
		||||
      - name: Login to DockerHub
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKERHUB_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
      - name: Pull nginx:alpine image
 | 
			
		||||
        run: docker pull nginx:alpine
 | 
			
		||||
 | 
			
		||||
      - name: Build Docker web server image
 | 
			
		||||
        run: make build-webserver
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -618,7 +618,19 @@ If the default certificate is also missing, nginx-proxy will:
 | 
			
		||||
 | 
			
		||||
## IPv6 Support
 | 
			
		||||
 | 
			
		||||
You can activate the IPv6 support for the nginx-proxy container by passing the value `true` to the `ENABLE_IPV6` environment variable:
 | 
			
		||||
### IPv6 Docker Networks
 | 
			
		||||
 | 
			
		||||
nginx-proxy support both IPv4 and IPv6 on Docker networks.
 | 
			
		||||
 | 
			
		||||
By default nginx-proxy will prefer IPv4: if a container can be reached over both IPv4 and IPv6, only its IPv4 will be used.
 | 
			
		||||
 | 
			
		||||
This can be changed globally by setting the environment variable `PREFER_IPV6_NETWORK` to `true` on the proxy container: with this setting the proxy will only use IPv6 for containers that can be reached over both IPv4 and IPv6.
 | 
			
		||||
 | 
			
		||||
IPv4 and IPv6 are never both used at the same time on containers that use both IP stacks to avoid artificially inflating the effective round robin weight of those containers.
 | 
			
		||||
 | 
			
		||||
### Listening on IPv6
 | 
			
		||||
 | 
			
		||||
By default the nginx-proxy container will only listen on IPv4. To enable listening on IPv6 too, set the `ENABLE_IPV6` environment variable to `true`:
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
docker run -d -p 80:80 -e ENABLE_IPV6=true -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								nginx.tmpl
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								nginx.tmpl
									
									
									
									
									
								
							@@ -23,6 +23,7 @@
 | 
			
		||||
{{- $_ := set $config "trust_downstream_proxy" ($globals.Env.TRUST_DOWNSTREAM_PROXY | default "true" | parseBool) }}
 | 
			
		||||
{{- $_ := set $config "enable_access_log" ($globals.Env.DISABLE_ACCESS_LOGS | default "false" | parseBool | not) }}
 | 
			
		||||
{{- $_ := set $config "enable_ipv6" ($globals.Env.ENABLE_IPV6 | default "false" | parseBool) }}
 | 
			
		||||
{{- $_ := set $config "prefer_ipv6_network" ($globals.Env.PREFER_IPV6_NETWORK | default "false" | parseBool) }}
 | 
			
		||||
{{- $_ := set $config "ssl_policy" ($globals.Env.SSL_POLICY | default "Mozilla-Intermediate") }}
 | 
			
		||||
{{- $_ := set $config "enable_debug_endpoint" ($globals.Env.DEBUG_ENDPOINT | default "false") }}
 | 
			
		||||
{{- $_ := set $config "hsts" ($globals.Env.HSTS | default "max-age=31536000") }}
 | 
			
		||||
@@ -76,7 +77,8 @@
 | 
			
		||||
     * The return value will be added to the dot dict with key "ip".
 | 
			
		||||
     */}}
 | 
			
		||||
{{- define "container_ip" }}
 | 
			
		||||
    {{- $ip := "" }}
 | 
			
		||||
    {{- $ipv4 := "" }}
 | 
			
		||||
    {{- $ipv6 := "" }}
 | 
			
		||||
    #     networks:
 | 
			
		||||
    {{- range sortObjectsByKeysAsc $.container.Networks "Name" }}
 | 
			
		||||
        {{- /*
 | 
			
		||||
@@ -91,17 +93,17 @@
 | 
			
		||||
            {{- /* Handle containers in host nework mode */}}
 | 
			
		||||
            {{- if (index $.globals.networks "host") }}
 | 
			
		||||
    #         both container and proxy are in host network mode, using localhost IP
 | 
			
		||||
                {{- $ip = "127.0.0.1" }}
 | 
			
		||||
                {{- $ipv4 = "127.0.0.1" }}
 | 
			
		||||
                {{- continue }}
 | 
			
		||||
            {{- end }}
 | 
			
		||||
            {{- range sortObjectsByKeysAsc $.globals.CurrentContainer.Networks "Name" }}
 | 
			
		||||
                {{- if and . .Gateway (not .Internal) }}
 | 
			
		||||
    #         container is in host network mode, using {{ .Name }} gateway IP
 | 
			
		||||
                    {{- $ip = .Gateway }}
 | 
			
		||||
                    {{- $ipv4 = .Gateway }}
 | 
			
		||||
                    {{- break }}
 | 
			
		||||
                {{- end }}
 | 
			
		||||
            {{- end }}
 | 
			
		||||
            {{- if $ip }}
 | 
			
		||||
            {{- if $ipv4 }}
 | 
			
		||||
                {{- continue }}
 | 
			
		||||
            {{- end }}
 | 
			
		||||
        {{- end }}
 | 
			
		||||
@@ -111,26 +113,41 @@
 | 
			
		||||
        {{- end }}
 | 
			
		||||
        {{- /*
 | 
			
		||||
             * Do not emit multiple `server` directives for this container if it
 | 
			
		||||
             * is reachable over multiple networks.  This avoids accidentally
 | 
			
		||||
             * inflating the effective round-robin weight of a server due to the
 | 
			
		||||
             * redundant upstream addresses that nginx sees as belonging to
 | 
			
		||||
             * is reachable over multiple networks or multiple IP stacks. This avoids 
 | 
			
		||||
             * accidentally inflating the effective round-robin weight of a server due
 | 
			
		||||
             * to the redundant upstream addresses that nginx sees as belonging to
 | 
			
		||||
             * distinct servers.
 | 
			
		||||
             */}}
 | 
			
		||||
        {{- if $ip }}
 | 
			
		||||
        {{- if or $ipv4 $ipv6 }}
 | 
			
		||||
    #         {{ .Name }} (ignored; reachable but redundant)
 | 
			
		||||
            {{- continue }}
 | 
			
		||||
        {{- end }}
 | 
			
		||||
    #         {{ .Name }} (reachable)
 | 
			
		||||
        {{- if and . .IP }}
 | 
			
		||||
            {{- $ip = .IP }}
 | 
			
		||||
        {{- else }}
 | 
			
		||||
    #             /!\ No IP for this network!
 | 
			
		||||
            {{- $ipv4 = .IP }}
 | 
			
		||||
        {{- end }}
 | 
			
		||||
        {{- if and . .GlobalIPv6Address }}
 | 
			
		||||
            {{- $ipv6 = .GlobalIPv6Address }}
 | 
			
		||||
        {{- end }}
 | 
			
		||||
        {{- if and (empty $ipv4) (empty $ipv6) }}
 | 
			
		||||
    #             /!\ No IPv4 or IPv6 for this network!
 | 
			
		||||
        {{- end }}
 | 
			
		||||
    {{- else }}
 | 
			
		||||
    #         (none)
 | 
			
		||||
    {{- end }}
 | 
			
		||||
    #     IP address: {{ if $ip }}{{ $ip }}{{ else }}(none usable){{ end }}
 | 
			
		||||
    {{- $_ := set $ "ip" $ip }}
 | 
			
		||||
    {{ if and $ipv6 $.globals.config.prefer_ipv6_network }}
 | 
			
		||||
    #     IPv4 address: {{ if $ipv4 }}{{ $ipv4 }} (ignored; reachable but IPv6 prefered){{ else }}(none usable){{ end }}
 | 
			
		||||
    #     IPv6 address: {{ $ipv6 }}
 | 
			
		||||
        {{- $_ := set $ "ip" (printf "[%s]" $ipv6) }}
 | 
			
		||||
    {{- else }}
 | 
			
		||||
    #     IPv4 address: {{ if $ipv4 }}{{ $ipv4 }}{{ else }}(none usable){{ end }}
 | 
			
		||||
    #     IPv6 address: {{ if $ipv6 }}{{ $ipv6 }}{{ if $ipv4 }} (ignored; reachable but IPv4 prefered){{ end }}{{ else }}(none usable){{ end }}
 | 
			
		||||
        {{- if $ipv4 }}
 | 
			
		||||
            {{- $_ := set $ "ip" $ipv4 }}
 | 
			
		||||
        {{- else if $ipv6}}
 | 
			
		||||
            {{- $_ := set $ "ip" (printf "[%s]" $ipv6) }}
 | 
			
		||||
        {{- end }}
 | 
			
		||||
    {{- end }}
 | 
			
		||||
{{- end }}
 | 
			
		||||
 | 
			
		||||
{{- /*
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								test/test_ipv6/test_ipv6_prefer_ipv4_network.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								test/test_ipv6/test_ipv6_prefer_ipv4_network.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_forwards_to_ipv4_only_network(docker_compose, nginxproxy):
 | 
			
		||||
    r = nginxproxy.get("http://ipv4only.nginx-proxy.tld/port")
 | 
			
		||||
    assert r.status_code == 200   
 | 
			
		||||
    assert r.text == "answer from port 80\n"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_forwards_to_dualstack_network(docker_compose, nginxproxy):
 | 
			
		||||
    r = nginxproxy.get("http://dualstack.nginx-proxy.tld")
 | 
			
		||||
    assert r.status_code == 200   
 | 
			
		||||
    assert "Welcome to nginx!" in r.text
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_dualstack_network_prefer_ipv4_config(docker_compose, nginxproxy):
 | 
			
		||||
    conf = nginxproxy.get_conf().decode('ASCII')
 | 
			
		||||
    assert "IPv6 address: fd00:cafe:face:feed::2 (ignored; reachable but IPv4 prefered)" in conf
 | 
			
		||||
    assert "server 172.16.20.2:80;" in conf
 | 
			
		||||
							
								
								
									
										45
									
								
								test/test_ipv6/test_ipv6_prefer_ipv4_network.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								test/test_ipv6/test_ipv6_prefer_ipv4_network.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
version: "2"
 | 
			
		||||
 | 
			
		||||
networks:
 | 
			
		||||
  ipv4net:
 | 
			
		||||
    ipam:
 | 
			
		||||
      config:
 | 
			
		||||
        - subnet: 172.16.10.0/24
 | 
			
		||||
  dualstacknet:
 | 
			
		||||
    enable_ipv6: true
 | 
			
		||||
    ipam:
 | 
			
		||||
      config:
 | 
			
		||||
        - subnet: 172.16.20.0/24
 | 
			
		||||
        - subnet: fd00:cafe:face:feed::/64
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  ipv4only:
 | 
			
		||||
    image: web
 | 
			
		||||
    expose:
 | 
			
		||||
      - "80"
 | 
			
		||||
    environment:
 | 
			
		||||
      WEB_PORTS: 80
 | 
			
		||||
      VIRTUAL_HOST: ipv4only.nginx-proxy.tld
 | 
			
		||||
    networks:
 | 
			
		||||
      ipv4net:
 | 
			
		||||
        ipv4_address: 172.16.10.2
 | 
			
		||||
 | 
			
		||||
  dualstack:
 | 
			
		||||
    image: nginx:alpine
 | 
			
		||||
    environment:
 | 
			
		||||
      VIRTUAL_HOST: dualstack.nginx-proxy.tld
 | 
			
		||||
    networks:
 | 
			
		||||
      dualstacknet:
 | 
			
		||||
        ipv4_address: 172.16.20.2
 | 
			
		||||
        ipv6_address: fd00:cafe:face:feed::2
 | 
			
		||||
 | 
			
		||||
  sut:
 | 
			
		||||
    image: nginxproxy/nginx-proxy:test
 | 
			
		||||
    volumes:
 | 
			
		||||
      - /var/run/docker.sock:/tmp/docker.sock:ro
 | 
			
		||||
    networks:
 | 
			
		||||
      ipv4net:
 | 
			
		||||
        ipv4_address: 172.16.10.3
 | 
			
		||||
      dualstacknet:
 | 
			
		||||
        ipv4_address: 172.16.20.3
 | 
			
		||||
        ipv6_address: fd00:cafe:face:feed::3
 | 
			
		||||
							
								
								
									
										19
									
								
								test/test_ipv6/test_ipv6_prefer_ipv6_network.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								test/test_ipv6/test_ipv6_prefer_ipv6_network.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_forwards_to_ipv4_only_network(docker_compose, nginxproxy):
 | 
			
		||||
    r = nginxproxy.get("http://ipv4only.nginx-proxy.tld/port")
 | 
			
		||||
    assert r.status_code == 200   
 | 
			
		||||
    assert r.text == "answer from port 80\n"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_forwards_to_dualstack_network(docker_compose, nginxproxy):
 | 
			
		||||
    r = nginxproxy.get("http://dualstack.nginx-proxy.tld")
 | 
			
		||||
    assert r.status_code == 200   
 | 
			
		||||
    assert "Welcome to nginx!" in r.text
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_dualstack_network_prefer_ipv6_config(docker_compose, nginxproxy):
 | 
			
		||||
    conf = nginxproxy.get_conf().decode('ASCII')
 | 
			
		||||
    assert "IPv4 address: 172.16.20.2 (ignored; reachable but IPv6 prefered)" in conf
 | 
			
		||||
    assert "server [fd00:cafe:face:feed::2]:80;" in conf
 | 
			
		||||
							
								
								
									
										47
									
								
								test/test_ipv6/test_ipv6_prefer_ipv6_network.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								test/test_ipv6/test_ipv6_prefer_ipv6_network.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
version: "2"
 | 
			
		||||
 | 
			
		||||
networks:
 | 
			
		||||
  ipv4net:
 | 
			
		||||
    ipam:
 | 
			
		||||
      config:
 | 
			
		||||
        - subnet: 172.16.10.0/24
 | 
			
		||||
  dualstacknet:
 | 
			
		||||
    enable_ipv6: true
 | 
			
		||||
    ipam:
 | 
			
		||||
      config:
 | 
			
		||||
        - subnet: 172.16.20.0/24
 | 
			
		||||
        - subnet: fd00:cafe:face:feed::/64
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  ipv4only:
 | 
			
		||||
    image: web
 | 
			
		||||
    expose:
 | 
			
		||||
      - "80"
 | 
			
		||||
    environment:
 | 
			
		||||
      WEB_PORTS: 80
 | 
			
		||||
      VIRTUAL_HOST: ipv4only.nginx-proxy.tld
 | 
			
		||||
    networks:
 | 
			
		||||
      ipv4net:
 | 
			
		||||
        ipv4_address: 172.16.10.2
 | 
			
		||||
 | 
			
		||||
  dualstack:
 | 
			
		||||
    image: nginx:alpine
 | 
			
		||||
    environment:
 | 
			
		||||
      VIRTUAL_HOST: dualstack.nginx-proxy.tld
 | 
			
		||||
    networks:
 | 
			
		||||
      dualstacknet:
 | 
			
		||||
        ipv4_address: 172.16.20.2
 | 
			
		||||
        ipv6_address: fd00:cafe:face:feed::2
 | 
			
		||||
 | 
			
		||||
  sut:
 | 
			
		||||
    image: nginxproxy/nginx-proxy:test
 | 
			
		||||
    volumes:
 | 
			
		||||
      - /var/run/docker.sock:/tmp/docker.sock:ro
 | 
			
		||||
    environment:
 | 
			
		||||
      PREFER_IPV6_NETWORK: "true"
 | 
			
		||||
    networks:
 | 
			
		||||
      ipv4net:
 | 
			
		||||
        ipv4_address: 172.16.10.3
 | 
			
		||||
      dualstacknet:
 | 
			
		||||
        ipv4_address: 172.16.20.3
 | 
			
		||||
        ipv6_address: fd00:cafe:face:feed::3
 | 
			
		||||
		Reference in New Issue
	
	Block a user