A white origami dove. The logo of the blog

rlang // tech

How to run Docker on IPv6

When running Docker on an IPv6 only host it can be quite complicated to set it up properly. This blog post will help you to get your container running on an IPv6 only infrastructure and avoid the most common gotchas.

Prerequisites

A word of caution

The first thing to note is that running on an IPv6 only host, it means that only v6 networking is generally possible. So any communication running over v4 won’t be possible. For example lets try DNS resolution. If I try to get the IP for google.com on my IPv6 host I get the following response:

$ dig www.google.com

...

;; ANSWER SECTION:
www.google.com.		217	IN	A	142.250.186.68

;; Query time: 0 msec
;; SERVER: 2a01:4ff:ff00::add:2#53(2a01:4ff:ff00::add:2) (UDP)
...

One thing we can immediately see is, that the DNS server that answered our request has an IPv6 address, but what happens now if I tell dig to resolve the query via an IPv4 DNS server ?

$ dig @1.1.1.1 www.google.com
;; communications error to 1.1.1.1#53: timed out

The request timed out as we expected because our host is not capable of using the IPv4 protocol. The same behavior will be observable in our Docker containers unless we explicitly tell Docker to run in IPv6 mode. So, let’s get going!

Configure the docker daemon

To configure the daemon(assuming you use dockerd), we need to edit the daemon.json. On linux hosts you can find the file under /etc/docker/daemon.json, if you’re running on another system consult the documentation of docker engine. If the file does not exist simply create it.

In order to fix the problem with the DNS resolution we need to tell the daemon to use IPv6 DNS servers. In this scenario I provided cloudflares DNS server. Additionally, we also instruct the daemon to enable v6 networking, as well as creating the default network with ULA addresses.

{
  "IPv6": true,
  "fixed-cidr-v6": "fd00:dead:beef::/48",
  "dns": [
    "2606:4700:4700::1111",
    "2606:4700:4700::1001"
  ],
  "ip6tables": true
}

Retrieving the prefix

To make your container globally reachable, we have to create an IPv6 network based on the global prefix of your host. In simplified terms, the global prefix is a 48-bit unique prefix that is used to route packets to your network. To find your IPv6 global prefix, you can execute ip -6 addr show. You want to search for a similar entry like this:

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
   ...
    inet6 2a01:4f8:1c0c:66b3::1/64 scope global noprefixroute
       valid_lft forever preferred_lft forever
   ...

As you can see, my hoster assigned me a /64 prefix. That means that the first 64 bits of the IPv6 address are fixed. So, in my example, 2a01:04f8:1c0c:66b3 is the fixed part. If we would fully write out the IP, it would look like this 2a01: 04f8:1c0c:66b3:0000:0000:0000:0001.

Creating the IPv6 network

Now let’s choose a smaller subnet for our public network. For example lets choose a /80 subnet like 2a01:4f8:1c0c:66b3:1::/80 . This will ensure containers in that network will receive a globally routable address, which means that those containers can be reached from the outside, unless blocked by a firewall or something similar.

$ docker network create --IPv6 --subnet 2a01:4f8:1c0c:66b3:1::/80 public

Run your container

Last but not the least run a container and attach it to our public network:

docker run --rm --network public -p [::]:8080:80 traefik/whoami

And if we now have a look at our container it has been assigned a globally routable IPv6 address. That we can reach from the internet.

$ docker inspect 31cb810f7c00
...
            "Networks": {
                "public": {
                    ...
                    "IPv6Gateway": "2a01:4f8:1c0c:66b3:1::1",
                    "GlobalIPv6Address": "2a01:4f8:1c0c:66b3:1::3",
                }
...

#docker #ipv6 #tech