6️⃣ Docker supports IPv6 addressing and IPv6 network builds.
🔇 But IPv6 is not enabled by default.
🔊 Here's how to turn on IPv6.
🧱 Plus how to build three different v6 networks; the default docker0 bridge network, a user-defined bridge network, and an IPvlan network with access to the public Internet.
🎬 TL;DR? Video link at the foot of the page 👇
How to enable Docker IPv6 support
The official docs are here, but I think they could have done a better job with this. Here's my version.
-
Create a JSON file
/etc/docker/daemon.json
and write to it a key ofipv6
with a value oftrue
and a key offixed-cidr-v6
with a value of your chosen IPv6 prefix. This prefix will be assigned to the default docker bridge network,docker 0
.
{ "ipv6": true, "fixed-cidr-v6": "2001:db8:abc1::/64" }
Save the file.
Restart docker:
systemctl restart docker
.-
Execute the command
docker network ls
. Unless you have previously created your own custom networks, you will see the following:
$ docker network ls NETWORK ID NAME DRIVER SCOPE 9ac6696dded9 bridge bridge local bcc12f998444 host host local 32126ee8d073 none null local
-
Inspect the default docker 'bridge' network with
docker network inspect bridge
. You should see your chosen IPv6 network listed, as well as a gateway address.
joe@ub6:~$ docker network inspect bridge [ { "Name": "bridge", "Id": "9ac6696dded9bec310d3451552df8b399503b09b6019b879e08914f6dd056a9d", "Created": "2021-08-25T08:10:48.713298521Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": true, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.17.0.0/16", "Gateway": "172.17.0.1" }, { "Subnet": "2001:db8:abc1::/64", "Gateway": "2001:db8:abc1::1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": { "com.docker.network.bridge.default_bridge": "true", "com.docker.network.bridge.enable_icc": "true", "com.docker.network.bridge.enable_ip_masquerade": "true", "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", "com.docker.network.bridge.name": "docker0", "com.docker.network.driver.mtu": "1500" }, "Labels": {} } ]
. -
Execute the
ip add
command and thedocker0
interface now shows an IPv6 address:
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:dc:01:46:7a brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever inet6 2001:db8:abc1::1/64 scope global valid_lft forever preferred_lft forever
Create an IPv6 container
-
Create a container as usual. By default this is assigned to the docker0 bridge network and has an IPv4 & IPv6 address:
$ docker run -di --name alpine6-1 alpine 0f1e634aacf45bfccca1494d6804bbaeac21b870152ceebdaeea8ad72ae27b3d $ docker exec alpine6-1 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 22: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever inet6 2001:db8:abc1::242:ac11:2/64 scope global flags 02 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe11:2/64 scope link valid_lft forever preferred_lft forever
. -
Ping the local gateway:
joe@ub6:~$ docker exec alpine6-1 ping6 2001:db8:abc1::1 PING 2001:db8:abc1::1 (2001:db8:abc1::1): 56 data bytes 64 bytes from 2001:db8:abc1::1: seq=0 ttl=64 time=0.133 ms 64 bytes from 2001:db8:abc1::1: seq=1 ttl=64 time=0.143 ms 64 bytes from 2001:db8:abc1::1: seq=2 ttl=64 time=0.123 ms
- The docker0 network is the default bridge network. By default, containers assigned to it will now have IPv4 and IPv6 connectivity to the local gateway and each other.
- However, due to limitations with the docker0 network, the advice from docker is that this network should not be used in Production environments, user-defined bridge networks should be used instead. In the next section I will build one such custom network.
User-defined bridge network build
- The user-defined bridge network is similar to the docker0 network; it resides on the docker host, and the host acts as the network's gateway.
- The containers assigned to a user-defined bridge network have access to other containers on the same network, to the gateway and to external networks.
- However, because the docker host performs the role of a gateway, external networks will need to have the necessary routing information to be able to communicate with the custom network. See the diagram below.
- In this example, the external server, by default, will not have a route to the user-defined bridge network's subnet, named my-net1 (2001:db8:1::1/64).
- The docker host acts as the gateway between the external network (2001:db8:eeee::/64) and my-net1.
- The containers attached to my-net1 will be able to send traffic to external networks, using the docker host as their default gateway, but return traffic will fail, the external server will lack the required information to return traffic to the containers.
- Therefore, this situation can be improved by adding a static route to the external server, pointing to 2001:db8:1::/64 via the docker host (2001:db8:eeee::/64).
Build Step-by-step
- Enable IPv6 for docker, see above for details.
-
Create a new IPv6 bridge network with the following command:
docker network create --subnet="<your-v6-prefix>" \ --gateway="your-gateway-address" \ --ipv6 \ <name-of-bridge-network>
Attach a container to this network using the
--network
flag fordocker run
.Verify with
docker network inspect <your-network>
.For external access, ensure destination networks have a route to
<your-v6-prefix>
.
Step-by-step example
# Create the network
$ docker network create --subnet="2001:db8:1::/64" \
--gateway="2001:db8:1::1" \
--ipv6 \
my-net1
# Create and attach a container
$ docker run -di --name alpine-1 \
--network my-net1 \
alpine:latest
# Verify
$ docker network inspect my-net1
[
{
"Name": "my-net1",
"Id": "c71cbef39206ccaf037db82a12993e59851c1ca9ad18f45975cff8a96f85240a",
"Created": "2021-09-02T08:13:38.448154937Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": true,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.20.0.0/16",
"Gateway": "172.20.0.1"
},
{
"Subnet": "2001:db8:1::/64",
"Gateway": "2001:db8:1::1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"736667de9c88a66fa14aa664eaad827bac7e68776288997361b5cda157949b65": {
"Name": "alpine-1",
"EndpointID": "88e56b4c8acbc4342ff0f3c0624a8e801b7fc3f4002339d14450645117ddd5b5",
"MacAddress": "02:42:ac:14:00:02",
"IPv4Address": "172.20.0.2/16",
"IPv6Address": "2001:db8:1::2/64"
}
},
"Options": {},
"Labels": {}
}
]
A Note on external communication for IPv6 containers
- In this example I've used the IPv6 prefix assigned to documentation for illustrative purposes.
- As previously mentioned, for containers on this network to communicate with external destinations, the external nodes will require the routing information to the internal v6 network.
- If you wish to provide your containers with access to the public internet, their attached network will need to be a globally routable prefix.
- In my home lab, my ISP currently only provides one globally routable prefix, assigned to my LAN. (I have a /56 assigned to my home but the ISP router is currently locked to only act as the gateway for a single /64 on the LAN).
- In this case, I opted for an IPvlan Layer 2 docker network, this uses the docker host's IPv6 network. In my case, this network is the ISP assigned globally routable network.
IPvlan network build
The official docs for IPvlan are here
- I'm using the default L2 mode.
- In this mode, the docker host parent interface will operate at Layer 2 only and switch traffic from containers to the external gateway, in my case this is my ISP router, which will then forward on the traffic to the public internet.
See the diagram below; the parent host interface, the containers, and the external gateway sit in the same IPv6 subnet.
Build Step-by-step
-
Create the IPvlan network with the following command:
docker network create -d ipvlan \ --subnet=<your-network-address>::/64 \ --gateway=<your-gateway-address> \ --ipv6 -o parent=<your-host-parent-interface> \ <your-network-name>
The parent interface sits in the same subnet as the gateway. Check 'ip addr' to confirm the interface name.
The created network is dual-stack, with both IPv4 and IPv6 addressing. Unless you specifically configure the IPv4 addressing, it is a /16 taken from the 172/8 range. -
Create a new container and connect it to the IPvlan network.
docker run -di --name alpine6-2 \ --network <your-network-name> alpine
This creates a container and attaches it to the IPvlan network.
The container is dual-stack, with an IPv4 address and IPv6 address.
Verify withip addr
. -
To test, ping an IPv6 site, such as
youtube.com
:
$ docker exec alpine6-2 ping youtube.com PING youtube.com (2a00:1450:4009:815::200e): 56 data bytes 64 bytes from 2a00:1450:4009:815::200e: seq=0 ttl=117 time=11.663 ms 64 bytes from 2a00:1450:4009:815::200e: seq=1 ttl=117 time=16.935 ms 64 bytes from 2a00:1450:4009:815::200e: seq=2 ttl=117 time=11.311 ms
TL;DR? 👇
Top comments (6)
Is there docker-compose example you can show ? Last time I tried by official docs, still failed :(
This worked for me. See notes inline
I'll write a new post to cover compose because there are a few more notes, but that file works for me.
thx. I will give it a try.
Thanks for the comment, I haven't tried that yet. I'll give it a go 👍
I think you should clearly state that the
2001:db8::/32
prefix is for doc only and that people should use ULA prefix (en.wikipedia.org/wiki/Unique_local...) (but awesome post !!)Follow up post with docker compose.
dev.to/joeneville_/build-an-ipv6-n...