Statically routed IPv6

Docker's own documentation was confusing for me, and it didn't work for me either: https://docs.docker.com/engine/userguide/networking/default_network/ipv6/

For this to work at all....

You will need all traffic for the entire range to go to your machine. This is because docker will send all traffic out through your regular interface, but return traffic will otherwise have nowhere to go. The easiest way of doing this, is to have two ranges. In my case:

What that looks like on the host:

iface br0 inet6 static
    gateway 2001:db8:2000:6::1
    address 2001:db8:2000:6::2/64

And on the router:

set routing-options rib inet6.0 static route 2001:db8:2005::/48 next-hop 2001:db8:2000:6::2

Docker

Docker will preferably want a /80 or bigger for a network, so it can let the IPv6-address of a container end with its MAC address and you prevent NDP neighbor cache invalidation issues in the Docker layer.

Using the default "bridge"-network dockerd creates.

Not recommended, because:

  • You would mix IPv4 NAT'd IP-addresses with routable IPv6-addresses.
  • You will forget that containers are reachable from the outside on IPv6 and you might risk services getting compromised without proper network filtering.

For this to work, you will have to make sure that when dockerd is started, it starts with --ipv6 and --fixed-cidr-v6 parameters.

On Ubuntu 16.04 with Docker 17.06.0-ce, I had to update /lib/systemd/system/docker.service's ExecStart:

ExecStart=/usr/bin/dockerd -H fd://

To:

ExecStart=/usr/bin/dockerd -H fd:// --ipv6 --fixed-cidr-v6=2001:db8:2005::/48

But beware! If you configure the entire /48 on the default docker bridge, you will not be able to split it up further later. Instead use a smaller /64 or /80:

ExecStart=/usr/bin/dockerd -H fd:// --ipv6 --fixed-cidr-v6=2001:db8:2005:1::/80

Then reload systemd, and restart docker:

systemctl daemon-reload
systemctl stop docker
systemctl start docker

If you now inspect the network bridge, you will see that IPv6 is enabled, and new containers started with it, will have a working, routed IPv6 address:

docker network inspect bridge

Make a "public" network instead.

This way, you do not have to modify configurations (like systemd's unit). Instead:

docker network create   --gateway       198.51.100.1                                            \
                        --subnet        198.51.100.0/24                                         \
                        --gateway       2001:db8:2005:cafe::1                                   \
                        --subnet        2001:db8:2005:cafe::/80                                 \
                        --ipv6                                                                  \
        -o "com.docker.network.bridge.enable_icc"="false"                                       \
        -o "com.docker.network.bridge.enable_ip_masquerade"="false"                             \
public

With this method, you can also easily give your host the first /80, and other container networks their own /80's as well. The result:

swarm-manager1 :: ~ » docker run --rm --network=public busybox ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:52:5E:CD:E2
          inet addr:198.51.100.2  Bcast:0.0.0.0  Mask:255.255.255.240
          inet6 addr: 2001:db8:2005:cafe::2/80 Scope:Global
          inet6 addr: fe80::42:52ff:fe5e:cde2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:3 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:410 (410.0 B)  TX bytes:220 (220.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

swarm-manager1 :: ~ »