IPv6 and Docker containers

Published on 2021-07-17.

I use Docker to run some services, which is working just fine. I however want to enable IPv6 in my containers so that these services can be reached over IPv6 connections. In this post I will show two ways of doing it.

For reference I am running Docker version 20.10.7 on Alpine Linux version 3.14.

To test the IPv6 setup I will be using an nginx container, and for making my life easier I created two DNS records pointing to the servers IP addresses:

This is the nginx configuration:

user    nginx;
worker_processes    auto;

error_log   /var/log/nginx/error.log notice;
pid         /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen 80;
        listen [::]:80;
        server_name localhost;

        location / {
            default_type text/plain;
            return 200 "Server addr: $server_addr\nRemote addr: $remote_addr\n";
        }
    }
}

It will return the container IP address and the clients IP address. Save the file and start the container:

# docker run --rm --name nginx -v $PWD/nginx.conf:/etc/nginx/nginx.conf:ro -p 192.0.2.10:8080:80 -p [2001:db8:1234::7357]:8080:80 nginx:alpine

If you are not familiar with Docker, this will happen:

The IPv6 address is surrounded by [] because that is the standard way of marking something as an IPv6 address and not for instance ports.

Once the container has started we can try and reach it via both IPv4 and IPv6:

$ curl http://docker-ipv4.pwd.re:8080
Server addr: 172.17.0.3
Remote addr: 198.51.100.97
$ curl http://docker-ipv6.pwd.re:8080
Server addr: 172.17.0.3
Remote addr: 172.17.0.1

This is surprising and a bit unexpected.

We, as a user, can reach the container via both IPv4 and IPv6 but internally all traffic gets translated into IPv4 by Docker. This means that the container has no idea what traffic type it is receiving. The IP addresses shown above are standard addresses Docker uses for it’s internal network.

This may or may not be a passable solution, it is indeed a fast way of getting IPv6 connectivity to a container but I want to expose the application inside to actual IPv6 traffic.

Another issue not shown above is that Docker doesn’t update ip6tables rules. By default iptables rules are added when a new container is created, so that it can be reached from the outside world (if necessary). This does not happen with ip6tables which means that new rules has to be added manually when a new container is created.

To enable IPv6 traffic in a container we need to enable IPv6 and specify a network from which the container will get an IP address, and to enable Docker to modify ip6tables rules we must enable experimental features and tell Docker to update the rules:

{
  "ipv6": true,
  "fixed-cidr-v6": "2001:db8:1::/64",
  "experimental": true,
  "ip6tables": true
}

Add that configuration in /etc/docker/daemon.json, restart the Docker daemon and create the nginx container once again. Now we will receive the following response from the container:

$ curl http://docker-ipv4.pwd.re:8080
Server addr: 172.17.0.3
Remote addr: 198.51.100.97
$ curl http://docker-ipv6.pwd.re:8080
Server addr: 2001:db8:1::832:be22:9
Remote addr: 2001:db8:2327:77ff:b298:2e5f:1911:2ef5

Now the container is receiving IPv6 traffic and ip6tables is automatically updated whenever a container is created or destroyed.