Mapping specific IPv6 addresses and ports in docker-compose.yml

I have a Raspberry Pi running a couple of docker-compose based configs, each of which has a host container and a swag container (for letsencrypt ssl).
The host itself has multiple static IPv6 addresses configured in /etc/network/interfaces.d.

Just the relevant parts of docker-compose.yml:

---
services:
  freshrss:
    image: lscr.io/linuxserver/freshrss:latest
  swag:
    image: lscr.io/linuxserver/swag:latest
    cap_add:
      - NET_ADMIN
    ports:
      # see /etc/network/interfaces.d/static-ipv6
      # the real address is in my actual /56 
      - "[2001:aa:bb:cc:dd:ee:ff:1]:443:443"
      - "[2001:aa:bb:cc:dd:ee:ff:1]:80:80"

The IPv6 address listed is one of the ones configured for the host.

This has all worked fine for the last year or so until today when I did an apt dist-upgrade:

Start-Date: 2024-06-27  15:23:12
Commandline: apt dist-upgrade
Requested-By: me (1000)
Upgrade:
containerd.io:arm64 (1.6.32-1, 1.7.18-1),
docker-compose-plugin:arm64 (2.27.0-1~debian.11~bullseye, 2.28.1-1~debian.11~bullseye),
docker-ce-cli:arm64 (5:26.1.3-1~debian.11~bullseye, 5:27.0.2-1~debian.11~bullseye),
docker-buildx-plugin:arm64 (0.14.0-1~debian.11~bullseye, 0.15.1-1~debian.11~bullseye),
docker-ce:arm64 (5:26.1.3-1~debian.11~bullseye, 5:27.0.2-1~debian.11~bullseye),
libndp0:arm64 (1.6-1+b1, 1.6-1+deb11u1),
docker-ce-rootless-extras:arm64 (5:26.1.3-1~debian.11~bullseye, 5:27.0.2-1~debian.11~bullseye),
python3-pil:arm64 (8.1.2+dfsg-0.3+deb11u1, 8.1.2+dfsg-0.3+deb11u2),
libgstreamer-plugins-base1.0-0:arm64 (1.18.4-2+deb11u1, 1.18.4-2+deb11u2)
End-Date: 2024-06-27  15:23:40

Now when I run docker compose up -d I get this error:

[+] Running 2/3
 ✔ Network freshrss_default  Created                                                                                                0.2s 
 ✔ Container freshrss        Started                                                                                                0.6s 
 ⠦ Container swag            Starting                                                                                               0.6s 
Error response from daemon: driver failed programming external connectivity on endpoint swag (4adc04e84deebc60ae527ddd916ca76e2ac9928271c8e63fa17b4572aacb22ff):
NAT is disabled, omit host address in port mapping [2001:aa:bb:cc:dd:ee:ff:1]:443:443/tcp, or use [::]::443 to open port 443 for IPv6-only
host port must not be specified in mapping [2001:aa:bb:cc:dd:ee:ff:1]:443:443/tcp because NAT is disabled
NAT is disabled, omit host address in port mapping [2001:aa:bb:cc:dd:ee:ff:1]:80:80/tcp, or use [::]::80 to open port 80 for IPv6-only
host port must not be specified in mapping [2001:aa:bb:cc:dd:ee:ff:1]:80:80/tcp because NAT is disabled

I can’t just listen on [::]::443 because there are multiple independent services running on the host, all accessible from their own https:// URLs

So, what is the “correct” way to configure a container to listen on a specific IPv6 IP and port? I don’t mind removing the static addresses from the host if there’s some way to configure docker itself to allocate them (as long as they are routable from my network, not just on this host).

I can’t answer your quest, as I never used ipv6 with docker. I would have expected that publishing ports on ipv6 was not possible in Docker versions < 27.0.

The release notes of Docker 27.0.1 contained several changes regarding ipv6: https://docs.docker.com/engine/release-notes/27.0/#networking.

What you experience must be an effect of it. Now the question is: is it intended or a bug. If you believe it’s a bug, you can raise an issue in https://github.com/moby/moby/issues.

Thank you @meyay for pointing me in the right direction. I was able to get it working again by assigning a custom bridge network with the com.docker.network.bridge.gateway_mode_ipv6 property set to nat.

Here is the (abbreviated) docker-compose file

---
services:
  freshrss:
    image: lscr.io/linuxserver/freshrss:latest
    networks:
      freshrss:
  swag:
    image: lscr.io/linuxserver/swag:latest
    networks:
      freshrss:
    cap_add:
      - NET_ADMIN
    ports:
      # see /etc/network/interfaces.d/static-ipv6
      - "[2001:aa:bb:cc:dd:ee:ff:1]:443:443"
      - "[2001:aa:bb:cc:dd:ee:ff:1]:80:80"
networks:
  freshrss:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.gateway_mode_ipv6: nat
    enable_ipv6: true

and with netstat -lnpW I can see that docker-proxy instances are listening to the correct IP and ports. It also passes the ultimate test in that I can open it in my browser.

Thanks again.

1 Like

Thank you for sharing your solution!