Manuel Bauer

Use Docker with full IPv6 support

This guide shows how to use enable full IPv6 support in Docker, which provides as benefit, that the original IPv6 address of the incoming request will reach the container. This is essential when for example running a mail server as container.

In a default Docker installation every exposed port is handled by the Docker userland proxy which proxies the connection to the target container. In a default installation this works great, but has as downsides the increased cpu usage and that only the original IPv4 address of the connection will reach the container.

Example of Docker userland proxy processes:

$ ps aux

...
root        1441  0.0  0.2 1221944 4952 ?        Sl   17:13   0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.17.0.2 -container-port 80
root        1449  0.0  0.2 1148212 4276 ?        Sl   17:13   0:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 80 -container-ip 172.17.0.2 -container-port 80
...

Test setup

  • IPv4 range: 192.168.1.0/24
  • Local machine: MacBook (IPv4: 192.168.1.104)
  • Debian VM (IPv4: 192.168.1.109) with normal Docker engine installation (Guide: [https://docs.docker.com/engine/install/debian/](https://docs.docker.com/engine/install/debian/)) and configured with bridge network

To see which IP address is used I used the whoami container from traefik which can be easily started with the following command:

$ docker run -d -p 80:80 traefik/whoami
Hint: When sending requests to the docker host, use the DNS or zeroconf name (in my case debian.local) to ensure that the correct IP address is used.

When now sending requests to the test host via curl command with IPv4 resolution, we see that the container is showing as RemoteAddr the correct IPv4 address of the host with sent the request (in my case the MacBook).

$ curl --ipv4 http://debian.local

Hostname: 136a826b2674
IP: 127.0.0.1
IP: 172.17.0.2
RemoteAddr: 192.168.1.104:55975
GET / HTTP/1.1
Host: debian.local
User-Agent: curl/7.64.1
Accept: */*

But when now using the IPv6 address we see that the Docker userland proxy is stepping in and don't set the correct RemoteAddr. In the response we see that the Gateway IP address of the docker bridge interface is used.

$ curl --ipv6 http://debian.local

Hostname: 136a826b2674
IP: 127.0.0.1
IP: 172.17.0.2
RemoteAddr: 172.17.0.1:60640
GET / HTTP/1.1
Host: debian.local
User-Agent: curl/7.64.1
Accept: */*

To fix this behaviour we will disable the userland proxy and enabling Docker IPv6 support with the use of IPv6 iptables (or ip6tables). Only downside of this method is, that the experimental flag needs to be set to true. But in over two years of using this method I don't got any problems with it.

After the change the iptables feature of the Linux kernel is taking care of the connection handling and routing with is much more resource efficient.

The apply the changes add the following data to the /etc/docker/daemon.json. If the file already exists add the fields to the existing JSON object.

{
    "userland-proxy": false,
    "ipv6": true,
    "ip6tables": true,
    "fixed-cidr-v6": "fd00:1::/64",
    "experimental": true
}

After a restart of the docker daemon we can now try it again and see which effect the changes have:

The IPv4 call is similar to the previous one, only we see now that the container have additional local IPv6 addresses:

$ curl --ipv4 http://debian.local

Hostname: 98418286d0be
IP: 127.0.0.1
IP: ::1
IP: 172.17.0.2
IP: fd00:1::242:ac11:2
IP: fe80::42:acff:fe11:2
RemoteAddr: 192.168.1.104:56256
GET / HTTP/1.1
Host: debian.local
User-Agent: curl/7.64.1
Accept: */*

But now the IPv6 request shows the correct IPv6 address of the request host.

$ curl --ipv6 http://debian.local

Hostname: 98418286d0be
IP: 127.0.0.1
IP: ::1
IP: 172.17.0.2
IP: fd00:1::242:ac11:2
IP: fe80::42:acff:fe11:2
RemoteAddr: [2004:b2:1f36:5d01:854:a123:b999:2ff7]:56269
GET / HTTP/1.1
Host: debian.local
User-Agent: curl/7.64.1
Accept: */*

Please keep in mind this only changes the default bridge docker interface. If you use custom docker networks you need to create them new with IPv6 support. This can be performed by adding the --ipv6 parameter and specifying a local IPv6 subnet to use.

This command will create a new docker network with IPv6 support called my-network and using the local IPv6 subnet fd00:2::/64.

docker network create --ipv6 --subnet=fd00:2::/64 my-network

I hope this guide helps everyone with using IPv6 with Docker. If you have any suggestions or questions you can find my contact information on the About page :)