Skip to content

Instantly share code, notes, and snippets.

@c0m4r
Last active November 3, 2024 09:40
Show Gist options
  • Save c0m4r/5497c6cbd7434e0cdf8dbd35060f724b to your computer and use it in GitHub Desktop.
Save c0m4r/5497c6cbd7434e0cdf8dbd35060f724b to your computer and use it in GitHub Desktop.
Docker inside IPv6-only host

Docker inside the IPv6-only host

This guide tries to explain workarounds required to live with Docker on a IPv6-only host.

NAT64 over dnsmasq

Docker hub itself works great with IPv6, but other services - like github - not so much. In order to enable communication with IPv4-only services wee need to use a proxy like https://github.via-ipv6.com/ or NAT64.

However, we don't necessarily want all traffic being routed through that service. Therfore, we're using dnsmasq to use NAT64 only for domains we need.

cat <<EOF > /etc/dnsmasq.conf
proxy-dnssec
no-resolv
no-poll
no-hosts
# Default DNS: quad9
server=2620:fe::fe
server=2620:fe::9
# Alternative DNS: https://meta.wikimedia.org/wiki/Wikimedia_DNS
server=2001:67c:930::1
# For specific hosts use Public NAT64 service: https://nat64.net/
server=/github.com/2a00:1098:2c::1
server=/github.com/2a00:1098:2b::1
server=/github.com/2a01:4f8:c2c:123f::1
server=/api.github.com/2a00:1098:2c::1
server=/api.github.com/2a00:1098:2b::1
server=/api.github.com/2a01:4f8:c2c:123f::1
server=/objects.githubusercontent.com/2a00:1098:2c::1
server=/objects.githubusercontent.com/2a00:1098:2b::1
server=/objects.githubusercontent.com/2a01:4f8:c2c:123f::1
EOF

Restart dnsmasq and set the DNS to ::1.

echo "nameserver ::1" > /etc/resolv.conf

It's crucial for our setup not to bind dnsmasq to a specific interface or address. We want it to listen on both IPv4 and IPv6 on all interfaces.

netstat -lnptu | grep dnsmasq
tcp        0      0 0.0.0.0:53              0.0.0.0:*               LISTEN      2341/dnsmasq
tcp        0      0 :::53                   :::*                    LISTEN      2341/dnsmasq
udp        0      0 0.0.0.0:53              0.0.0.0:*                           2341/dnsmasq
udp        0      0 :::53                   :::*                                2341/dnsmasq

Docker

We need to enable IPv6 support in Docker explicitly:

cat <<EOF > /etc/docker/daemon.json
{
  "ipv6": true,
  "fixed-cidr-v6": "2001:db8:1::/64",
  "experimental": true,
  "ip6tables": true
}
EOF

Then restart docker.

IPv6 docker is still experimental and I found fixed-cidr-v6 not working as expected, yet required, so I leave it be with an example subnet. We are going to set the proper network in docker compose.

Docker Compose

Now, for a specific use-case it's best to use Docker Composer to set the network and point to the dnsmasq daemon.

First of we define the network:

networks:
  net6:
    enable_ipv6: true
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: fd00:fd00:ffff::/64
          gateway: fd00:fd00:ffff::1

Because it's bridged and our dnsmasq is listening everywhere, the gateway fd00:fd00:ffff::1 will also be used as a DNS address.

Service-wide we're going to point the IPv6 network, assign an uniqe address within fd00:fd00:ffff::/64 network, and set the DNS.

services:
  nginx:
    networks:
      net6:
        ipv6_address: fd00:fd00:ffff::10
    dns: fd00:fd00:ffff::1
  php:
    networks:
      net6:
        ipv6_address: fd00:fd00:ffff::20
    dns: fd00:fd00:ffff::1

Now, let's test it out:

mkdir v6test && cd v6test
cat <<EOF > docker-compose.yml
version: '3.9'
services:
  nginx:
    image: nginx:latest
    networks:
      net6:
        ipv6_address: fd00:fd00:ffff::10
    dns: fd00:fd00:ffff::1
  php:
    image: php:fpm
    networks:
      net6:
        ipv6_address: fd00:fd00:ffff::20
    dns: fd00:fd00:ffff::1
networks:
  net6:
    enable_ipv6: true
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: fd00:fd00:ffff::/64
          gateway: fd00:fd00:ffff::1
EOF
docker compose up -d
docker compose exec -it php sh -c "curl -v https://ipv4.google.com" # this should fail
docker compose exec -it php sh -c "curl -v https://ipv6.google.com" # but this should work

In case something's not right, make sure to explicitly allow traffic to UDP/53 with DST: fd00:fd00:ffff::1

ip6tables -I INPUT -d fd00:fd00:ffff::1 -p udp --dport 53 -j ACCEPT -m comment --comment 'Allow ULA to dnsmasq'

IPv6 network considerations

In this example I use ULA. There is some controversy around it, but it's already widely (or rather wildly) used. The "proper" way is probably figure out a network setup using ordinary public IPv6 addresses from the subnet assigned to your host.

image

If you care about being compliant, these tools will help you generate some random ULA subnet:

Although, given current IPv6 adoption it will probably take another 20 years until anyone cares.


If you found this article helpful, please consider making donation to a charity on my behalf. Thank you.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment