Give IPv6 connectivity to its Docker containers using an IPv6 block from its ISP

It may seem surprising that a modern service like Docker does not offer IPv6 in containers by default, especially when in a network with IPv6.

In fact, for the same reason we saw in the introductory article, since the containers are in a virtual network, they cannot be reached by the box/router distributing the IPv6 subnet.

The same phenomenon can be observed with IPv4: each container has an IPv4 in a subnet separate from the one in which our host machine is located.

Illustration of a classic IPv4 home network

In order for the containers to have access to the Internet under these conditions, in IPv4 NAT is implemented:

42sh$ iptables -t nat -vnL POSTROUTING
Chain POSTROUTING (policy ACCEPT 3 packets, 228 bytes)
 pkts bytes target     prot opt in     out     source               destination
14713  978K MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0

Since NAT is generally not done on IPv6, nothing similar is done by Docker in this sense.

Docker as IPv6 router

Without IPv6 in a container, it is impossible for containers to address other services listening exclusively in IPv6 on the Internet.

In order for containerized programs to be able to connect to other services in IPv6, the Enable IPv6 option must be activated and the prefix to be used must be defined through the IPv6 Prefix option.

Be careful, it is not enough to define these options, it is also necessary that the box correctly routes the packets to the destinations of the containers to your machine.

This is why we need to take advantage of the other IPv6 blocks provided by our operator. By telling the box the address of the machine hosting our containers, it will route all packets to the containers without question.

So not everything can be done exclusively on the machine, the network must also be configured. Let’s start with that.

Set up IPv6 prefix delegation on the Freebox

The router will ask us for the address (IPv6) to which it should route the packets. We usually indicate a local link IP.

So we start by looking at our local IPv6 on the outgoing link to the router.

⚠️ Be careful, all interfaces have a local address, they all start with fe80:, they are only valid on the network card considered. If you get the wrong address, nothing will happen (it won’t break your network though).

In my case, it is the eth0 interface that is connected to the router:

42sh$ ip address show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether fd:54:01:98:cd:ba brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.42/24 brd 192.168.0.255 scope global dynamic noprefixroute eth0
       valid_lft 35141sec preferred_lft 35141sec
    inet6 2a01:...:2420:24ac:f101:c280:50c2/64 scope global noprefixroute
       valid_lft forever preferred_lft forever
    inet6 fe80::5a43:3580:173c:395e/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

My local IP is therefore fe80::5a43:3580:173c:395e.

It is this IP that I will indicate in the configuration of the router.

On the Freebox, the window for setting additional prefixes is in “Paramètres de la Freebox”, “Configuration IPv6”, under the “Général” tab. It is the “Délégation de préfixe” box that will interest us.

It looks like this:

Freebox IPv6 prefix delegation settings window

Always leave the first field empty, otherwise the box will not offer you IPv6 on the main network.

Indicate in the next empty field (normally the second one!) the local address retrieved earlier.

That’s all! The hardest part is over. Now let’s see the Docker configuration.

Setting up Docker for IPv6

We will not use the range to which our machine is connected. We are going to use a whole /64 range, the one for which we have given the local IP of our machine to the box.

Our prefix delegation correctly set up on the Freebox

According to the previous screenshot, our configuration file /etc/docker/daemon.json should look like:

{
  "ipv6": true,
  "fixed-cidr-v6": "2a01:1234:abcd:2421::/64"
}

We restart Docker and we can test:

42sh$ docker run -it alpine
/ # ip address show eth0
58: eth0@if59: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:09 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.9/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2a01:1234:abcd:2421:0:242:ac11:9/64 scope global flags 02
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe11:9/64 scope link
       valid_lft forever preferred_lft forever

If you have an IPv6 in addition to the usual IPv4, Docker is correctly configured. To find out if the configuration on the box side was successful, let’s do a ping in the container:

/ # ping ping6.online.net
PING ping6.online.net (2001:bc8:1::40): 56 data bytes
64 bytes from 2001:bc8:1::40: seq=0 ttl=52 time=11.008 ms
64 bytes from 2001:bc8:1::40: seq=1 ttl=52 time=8.822 ms
^C
--- ping6.online.net ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 8.822/9.915/11.008 ms

If the ping responds, it’s all good: your containers will now have access to and be accessible in IPv6.

Others use cases

This post is part of a series of posts on the use of additional IPv6 ranges: