Overview

Docker has supported IPv6 for quite some time, yet it remains disabled by default. This article documents enabling container IPv6 outbound capacity via NAT ULA on Debian 13. This method avoids complex routing broadcasts and mirrors the IPv4 experience almost identically.

Modifying Configuration

Edit Docker daemon configuration file:

1
sudo vim /etc/docker/daemon.json

Add the following configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{

  "log-driver": "json-file",

  "log-opts": {

    "max-size": "20m",

    "max-file": "3"

  },

  // Globally enable IPv6

  "ipv6": true,

  // Allocate ULA address block

  "fixed-cidr-v6": "fd00:dead:beef::/64",

  "experimental": true,

  // Enable NAT translation (ip6tables)

  "ip6tables": true,

  // Private address pools

  "default-address-pools": [

    {

      "base": "172.17.0.0/16",

      "size": 24

    },

    {

      "base": "fd00:dead:beef:100::/80",

      "size": 112

    }

  ]

}

Restart Docker for settings to take effect:

1
sudo systemctl restart docker

Usage

Verify the container now possesses IPv6 outbound capability:

1
2
3
# HTTP/2 200

sudo docker run --rm curlimages/curl curl -s -I -6 https://blog.zsh.moe

For Compose orchestrated container services, implementation is similarly straightforward (e.g., Warp Docker):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
services:

  warp:

    image: caomingjun/warp:slim-latest

    container_name: warp

    restart: unless-stopped

    networks:

      - warp-tunnel

    device_cgroup_rules:

      - 'c 10:200 rwm'

    environment:

      - WARP_SLEEP=2

      - GOST_ARGS=-L socks5://:1080?udp=true -L http://:1081

    cap_add:

      - MKNOD

      - AUDIT_WRITE

      - NET_ADMIN

    sysctls:

      - net.ipv6.conf.all.disable_ipv6=0

      - net.ipv4.conf.all.src_valid_mark=1

      # Enable IPv6 forwarding

      - net.ipv6.conf.all.forwarding=1

      # Accept Docker bridge routing advertisements

      - net.ipv6.conf.all.accept_ra=2

      - net.ipv4.ip_forward=1

    volumes:

      - ./data:/var/lib/cloudflare-warp

networks:

  warp-tunnel:

    name: warp-tunnel

    driver: bridge

    # Explicitly enable IPv6 support

    enable_ipv6: true

Start service:

1
sudo docker compose up -d

Inspect container-internal network interfaces to verify successful private IP capture:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
sudo docker exec warp ip -6 addr show

# Output Example

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000

    inet6 ::1/128 scope host

       valid_lft forever preferred_lft forever

2: eth0@if60: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP

    # IP allocated from the IPv6 ULA pool

    inet6 fd00:dead:beef:100::2/112 scope global nodad

       valid_lft forever preferred_lft forever

    inet6 fe80::3cc7:aaff:fed1:1680/64 scope link

       valid_lft forever preferred_lft forever

3: CloudflareWARP: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1280 state UNKNOWN qlen 500

    inet6 2606:4700:cf1:1000::3/128 scope global

       valid_lft forever preferred_lft forever

    inet6 fe80::6594:3a07:3299:4dbe/64 scope link stable-privacy

       valid_lft forever preferred_lft forever

Test Warp IPv6 proxy outbound capacity:

1
2
3
# HTTP/2 200

sudo docker exec warp curl -x socks5h://[::1]:1080 -6 https://blog.zsh.moe -v

Conclusion

We predefined a massive private address pool; Docker automatically allocates private IPv6s, transiting via host NAT outbound. Subnet division overheads bypassed, granting containers seamless dual-stack outbound abilities.

References: