Setting up IPv6 NAT in Docker
Yes, I know NAT on IPv6 is evil. But Docker's IPv6 implementation is so flawed that this is the only feasible way to get egress traffic from containers working somewhat reliably.
If you want your containers to be able to establish egress connections over IPv6 using the same source address as non-containerized services on the host - such as the reverse proxy - using NAT on IPv6 does make some sense.
Configuring Docker to use IPv6
First, install Docker:
sudo apt install docker.io docker-cli
Then configure Docker to enable IPv6:
- /etc/docker/daemon.json
{ "ipv6": true, "fixed-cidr-v6": "2001:db8:db8::/64" }
Restart Docker:
sudo systemctl restart docker
You should now see an inet6 address for the docker0 interface:
$ ip addr show dev docker0 docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:35:12:16:5c 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:db8::1/64 scope global valid_lft forever preferred_lft forever
Configuring the firewall to forward packets
By default, Linux does not allow packet forwarding, and Docker only manipulates the IPv4 forwarding rules when started, even when IPv6 is enabled. So you'll have to configure everything yourself.
sysctl values
Create a file called /etc/sysctl.d/packetforwarding.conf and add these lines:
- /etc/sysctl.d/packetforwarding.conf
net.ipv6.conf.all.forwarding=2 net.ipv6.conf.all.proxy_ndp=1
Then load them:
sudo sysctl -p /etc/sysctl.d/packetforwarding.conf
In some cases you have to reboot for this to actually delegate to your network interface. It can also help to set it manually once:
sudo sysctl -w net.ipv6.conf.ens3.forwarding=2 sudo sysctl -w net.ipv6.conf.ens3.proxy_ndp=1
(Change ens3 to the interface your server uses to connect to the internet.)
Firewall rules
Start by installing UFW:
sudo apt install ufw
By default, UFW changes your iptables/nftables rules to deny all ingress traffic, allow all egress traffic and disable routed traffic. Keep that in mind when you enable it.
Edit /etc/ufw/before6.rules to allow masquerading for the Docker subnet (or even a larger one if you want). Add these lines at the top of the file:
- /etc/ufw/before6.rules
# Docker *nat :POSTROUTING ACCEPT [0:0] -A POSTROUTING -s 2001:db8::/32 -o ens3 -j MASQUERADE COMMIT
(Change ens3 to the interface your server uses to connect to the internet.)
To allow IPv6 NAT on the interfaces dynamically generated by docker compose, set the default routing policy to Allow:
sudo ufw default allow routed
Finally, you have to allow (some) ingress traffic to avoid locking yourself out when you enable the firewall. You have two options:
1. Allow all ingress traffic:
sudo ufw default allow incoming
2. Allow ingress traffic on the ports you actually use:
sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp
This example allows only SSH, HTTP and HTTPS. Of course, add all the ports you want to be able to reach from the outside.
All that's left is to enable the firewall:
sudo ufw enable
Your Docker containers should now, hopefully, be able to access the internet over IPv6.