Cấu hình IPv6 cho Docker Containers: Hướng dẫn Dual-stack từng bước

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Một buổi sáng deploy lên server mới, mình mới phát hiện ra hosting đã cấp cho VPS một địa chỉ IPv6 xịn — nhưng toàn bộ container Docker vẫn đang chạy thuần IPv4. Lãng phí. Sau khoảng 2 tiếng ngồi đọc docs và thử lỗi, mình mới hiểu tại sao Docker không tự động dùng IPv6 dù host đã có sẵn.

Nếu VPS của bạn đã có IPv6 nhưng container vẫn chỉ nói chuyện qua IPv4, bài này giải quyết đúng vấn đề đó — từ bước bật daemon đến setup dual-stack hoàn chỉnh trong Docker Compose.

Quick Start: Bật IPv6 cho Docker trong 5 phút

Nếu bạn chỉ cần làm cho nhanh, đây là 3 bước tối giản:

Bước 1: Chỉnh file daemon.json

Mở (hoặc tạo) file /etc/docker/daemon.json:

sudo nano /etc/docker/daemon.json

Dán vào file (hoặc merge nếu đã có nội dung sẵn):

{
  "ipv6": true,
  "fixed-cidr-v6": "fd00::/80"
}

Bước 2: Restart Docker

sudo systemctl restart docker

Bước 3: Kiểm tra

docker network inspect bridge | grep -A 5 "EnableIPv6"

Thấy "EnableIPv6": true là Docker đã nhận cấu hình. Container mới tạo trong default bridge network từ đây sẽ được cấp địa chỉ IPv6 tự động.

Hiểu rõ hơn: Docker xử lý IPv6 như thế nào?

Lần đầu dùng Docker Compose cho dự án thực tế, mình mắc khá nhiều lỗi ngớ ngẩn. Trong đó có cái kinh điển nhất: enable IPv6 xong rồi ngồi thắc mắc tại sao container vẫn không kết nối được. Lý do là Docker có nhiều loại network, và mỗi loại phải cấu hình riêng.

Các loại network trong Docker

  • bridge (mặc định): Container trên cùng host giao tiếp qua NAT
  • host: Container dùng network của host, IPv6 hoạt động nếu host có
  • overlay: Dùng trong Docker Swarm, cần cấu hình riêng
  • macvlan: Container có địa chỉ MAC/IP riêng trên mạng vật lý

Bật ipv6: true trong daemon.json chỉ ảnh hưởng đến default bridge network. Mọi custom network bạn tạo trong Docker Compose — điểm này nhiều người bỏ qua — đều phải khai báo IPv6 riêng.

Dải địa chỉ IPv6 nên dùng

Với môi trường nội bộ (không cần route ra ngoài), dùng dải ULA (Unique Local Address) — tương tự 192.168.x.x trong IPv4:

fd00::/8   ← Private, dùng nội bộ là ổn

Ví dụ phổ biến:

  • fd00::/80 — cho bridge mặc định
  • fd00:db8:1::/64, fd00:db8:2::/64 — cho các custom network

/64 là chuẩn subnet IPv6 theo RFC. /80 vẫn chạy được, nhưng một số tool network và router xử lý sai prefix không phải /64. Dùng production thì gắn luôn /64 cho chắc ăn.

Dual-stack: Chạy cả IPv4 và IPv6 cùng lúc

Dual-stack đơn giản là container có cả hai: IPv4 để tương thích với hệ thống cũ, IPv6 để sẵn sàng cho tương lai. Không cần chọn một trong hai — và đây cũng là setup bạn nên dùng mặc định.

Tạo custom network dual-stack bằng 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

Kiểm tra kết quả:

docker network inspect myapp-network | grep -A 20 '"IPAM"'

Output sẽ có cả 2 block config IPv4 và IPv6:

"IPAM": {
    "Driver": "default",
    "Config": [
        {
            "Subnet": "172.20.0.0/24",
            "Gateway": "172.20.0.1"
        },
        {
            "Subnet": "fd00:db8::/64",
            "Gateway": "fd00:db8::1"
        }
    ]
}

Xem địa chỉ IPv6 của container

docker run -d --name web --network myapp-network nginx:alpine

# Cách 1: inspect
docker inspect web | grep -A 5 "GlobalIPv6"

# Cách 2: vào trong container xem
docker exec web ip -6 addr show

Cấu hình IPv6 trong Docker Compose

Thực tế thì mình hiếm khi dùng lệnh docker network create tay. Docker Compose quản lý network sẽ gọn hơn nhiều:

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

Khởi động stack và kiểm tra ping giữa các service:

docker compose up -d
docker compose exec web ping6 api

Nâng cao: Expose container qua IPv6 ra ngoài

Muốn client bên ngoài kết nối vào container qua IPv6? Có 2 hướng tùy theo nhu cầu.

Cách 1: Port mapping (đơn giản nhất)

Trong docker-compose.yml:

services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"       # IPv4
      - "[::]:80:80"  # IPv6 explicit

Nếu host đã bật IPv6 forwarding và daemon.json đã có ipv6: true, thì 80:80 thường tự map cả IPv4 lẫn IPv6 rồi — không cần khai báo riêng dòng thứ hai.

Cách 2: NDP Proxy (cho VPS có IPv6 public thật)

Nếu VPS có dải IPv6 public từ hosting (ví dụ 2001:db8::/48), bạn có thể dùng NDP proxy để container nhận IPv6 public — không qua NAT.

Bật NDP proxy trên host:

sudo sysctl net.ipv6.conf.all.proxy_ndp=1

Thêm proxy entry cho container:

sudo ip -6 neigh add proxy 2001:db8::2 dev eth0

Cách này phức tạp hơn, nhưng có lợi rõ khi container cần IPv6 public thật sự. Không qua NAT, không cần port mapping — traffic đi thẳng vào container. Phù hợp với bare metal hoặc dedicated server có dải IPv6 public.

Tips thực tế

1. Kiểm tra IPv6 connectivity từ trong container

docker exec -it mycontainer ping6 google.com
docker exec -it mycontainer curl -6 https://ipv6.google.com

2. Tránh trùng subnet

Mỗi network phải có subnet riêng — cả IPv4 lẫn IPv6. Docker báo lỗi khi trùng, nhưng message đôi khi khó đọc. Xem nhanh tất cả subnet đang dùng:

docker network ls -q | xargs docker network inspect | grep '"Subnet"'

3. Persist IPv6 forwarding qua reboot

Thêm vào /etc/sysctl.conf để không mất sau khi reboot:

net.ipv6.conf.all.forwarding=1

Áp dụng ngay mà không cần reboot:

sudo sysctl -p

4. Nginx trong container cần listen IPv6 riêng

Nếu container chạy Nginx, thêm dòng này vào server block:

server {
    listen 80;
    listen [::]:80;  # thêm dòng này cho IPv6

    server_name example.com;
}

5. Debug traffic với tcpdump

sudo tcpdump -i docker0 ip6

Chạy lệnh này khi nghi ngờ traffic IPv6 không đi đúng hướng — sẽ thấy ngay có gói tin IPv6 chạy qua interface hay không.

Tóm lại

Ba tầng cần nhớ khi cấu hình IPv6 cho Docker: daemon, network, và container. Mỗi tầng độc lập — bật ở daemon không có nghĩa là custom network tự có IPv6. Phần lớn project chỉ cần ipv6: true trong daemon.json và enable_ipv6: true trong Compose file là xong.

Mình thường setup dual-stack từ đầu cho mọi project — thêm vài dòng config nhưng đỡ đau đầu về sau khi hosting thay đổi chính sách IPv4, hoặc khi cần debug network giữa các container.

Share: