One morning while deploying to a new server, I discovered the hosting provider had assigned a proper IPv6 address to the VPS — but all my Docker containers were still running on pure IPv4. What a waste. After about two hours of reading docs and trial-and-error, I finally understood why Docker doesn’t automatically use IPv6 even when the host already has it.
If your VPS has IPv6 but your containers are still only communicating over IPv4, this article addresses exactly that problem — from enabling the daemon to setting up a complete dual-stack configuration in Docker Compose.
Quick Start: Enable IPv6 for Docker in 5 Minutes
If you just need to get it done fast, here are the 3 minimal steps:
Step 1: Edit daemon.json
Open (or create) the file /etc/docker/daemon.json:
sudo nano /etc/docker/daemon.json
Paste the following (or merge if content already exists):
{
"ipv6": true,
"fixed-cidr-v6": "fd00::/80"
}
Step 2: Restart Docker
sudo systemctl restart docker
Step 3: Verify
docker network inspect bridge | grep -A 5 "EnableIPv6"
Seeing "EnableIPv6": true means Docker has picked up the configuration. Newly created containers in the default bridge network will now automatically be assigned an IPv6 address.
Going Deeper: How Does Docker Handle IPv6?
When I first used Docker Compose for a real project, I made quite a few rookie mistakes. One of the most classic: enabling IPv6 and then wondering why the container still couldn’t connect. The reason is that Docker has multiple network types, and each one requires its own configuration.
Docker Network Types
- bridge (default): Containers on the same host communicate via NAT
- host: Container uses the host’s network stack; IPv6 works if the host has it
- overlay: Used in Docker Swarm, requires separate configuration
- macvlan: Containers get their own MAC/IP address on the physical network
Setting ipv6: true in daemon.json only affects the default bridge network. Any custom network you create in Docker Compose — and this is what many people miss — must declare IPv6 support separately.
Which IPv6 Address Range to Use
For internal environments (no external routing needed), use the ULA (Unique Local Address) range — analogous to 192.168.x.x in IPv4:
fd00::/8 ← Private, suitable for internal use
Common examples:
fd00::/80— for the default bridgefd00:db8:1::/64,fd00:db8:2::/64— for custom networks
/64 is the standard IPv6 subnet size per RFC. /80 works, but some networking tools and routers mishandle prefixes that aren’t /64. For production, stick with /64 to be safe.
Dual-Stack: Running IPv4 and IPv6 Simultaneously
Dual-stack simply means a container has both: IPv4 for backward compatibility with older systems, and IPv6 for future readiness. You don’t have to pick one — and this is the setup you should use by default.
Create a Dual-Stack Custom Network via CLI
docker network create \
--driver bridge \
--subnet 172.20.0.0/24 \
--gateway 172.20.0.1 \
--ipv6 \
--subnet fd00:db8::/64 \
--gateway fd00:db8::1 \
myapp-network
Verify the result:
docker network inspect myapp-network | grep -A 20 '"IPAM"'
The output will contain both IPv4 and IPv6 config blocks:
"IPAM": {
"Driver": "default",
"Config": [
{
"Subnet": "172.20.0.0/24",
"Gateway": "172.20.0.1"
},
{
"Subnet": "fd00:db8::/64",
"Gateway": "fd00:db8::1"
}
]
}
View a Container’s IPv6 Address
docker run -d --name web --network myapp-network nginx:alpine
# Option 1: inspect
docker inspect web | grep -A 5 "GlobalIPv6"
# Option 2: check from inside the container
docker exec web ip -6 addr show
Configuring IPv6 in Docker Compose
In practice, I rarely use the docker network create command manually. Managing networks through Docker Compose is much cleaner:
services:
web:
image: nginx:alpine
networks:
- frontend
api:
image: node:18-alpine
networks:
- frontend
- backend
db:
image: postgres:15
networks:
- backend
networks:
frontend:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 172.20.0.0/24
gateway: 172.20.0.1
- subnet: fd00:db8:1::/64
gateway: fd00:db8:1::1
backend:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 172.21.0.0/24
gateway: 172.21.0.1
- subnet: fd00:db8:2::/64
gateway: fd00:db8:2::1
Start the stack and test ping between services:
docker compose up -d
docker compose exec web ping6 api
Advanced: Exposing Containers via IPv6 Externally
Want external clients to connect to your containers over IPv6? There are two approaches depending on your needs.
Option 1: Port Mapping (Simplest)
In docker-compose.yml:
services:
web:
image: nginx:alpine
ports:
- "80:80" # IPv4
- "[::]:80:80" # IPv6 explicit
If the host already has IPv6 forwarding enabled and daemon.json has ipv6: true, then 80:80 usually maps both IPv4 and IPv6 automatically — the second line isn’t strictly necessary.
Option 2: NDP Proxy (For VPS with Real Public IPv6)
If your VPS has a public IPv6 range from the hosting provider (e.g. 2001:db8::/48), you can use NDP proxy to give containers a real public IPv6 address — no NAT involved.
Enable NDP proxy on the host:
sudo sysctl net.ipv6.conf.all.proxy_ndp=1
Add a proxy entry for the container:
sudo ip -6 neigh add proxy 2001:db8::2 dev eth0
This approach is more complex, but it pays off when containers genuinely need a real public IPv6 address. No NAT, no port mapping — traffic goes directly into the container. Best suited for bare metal or dedicated servers with a public IPv6 block.
Practical Tips
1. Test IPv6 Connectivity from Inside a Container
docker exec -it mycontainer ping6 google.com
docker exec -it mycontainer curl -6 https://ipv6.google.com
2. Avoid Subnet Conflicts
Each network must have its own subnet — for both IPv4 and IPv6. Docker reports an error on conflicts, but the message can be hard to read. Quickly view all subnets currently in use:
docker network ls -q | xargs docker network inspect | grep '"Subnet"'
3. Persist IPv6 Forwarding Across Reboots
Add this to /etc/sysctl.conf so the setting survives a reboot:
net.ipv6.conf.all.forwarding=1
Apply immediately without rebooting:
sudo sysctl -p
4. Nginx Inside a Container Needs to Listen on IPv6 Explicitly
If a container runs Nginx, add this line to the server block:
server {
listen 80;
listen [::]:80; # add this line for IPv6
server_name example.com;
}
5. Debug Traffic with tcpdump
sudo tcpdump -i docker0 ip6
Run this command when you suspect IPv6 traffic isn’t flowing correctly — you’ll immediately see whether any IPv6 packets are passing through the interface.
Summary
There are three layers to keep in mind when configuring IPv6 for Docker: the daemon, the network, and the container. Each layer is independent — enabling it at the daemon level does not mean custom networks automatically get IPv6. For most projects, setting ipv6: true in daemon.json and enable_ipv6: true in the Compose file is all you need.
I typically set up dual-stack from the start on every project — it’s just a few extra lines of config, but it saves a lot of headaches later when a hosting provider changes their IPv4 policy, or when you need to debug network issues between containers.

