4707 字
24 分钟
Docker 网络详解:从 bridge 到 IPv6
2026-11-15

上一篇 《如何在Linux部署Docker&Compose》 里,我们用 docker compose up -d 就把 Nginx 和 Redis 跑起来了,还顺手映射了端口。但很多人对 Docker 网络的理解也就停留在「加一行 ports 就能访问」这一步,一旦遇到容器之间互访不通、端口映射了外网却连不上、想开 IPv6 不知道从哪下手,就会卡住。这篇就把 Docker 网络一次讲透:五种网络模式的原理与选型、端口映射的正确姿势、自定义网络的容器互访、host 模式的实战与坑、IPv6 的完整开启方法,最后给一套排障工具箱。看完这篇,Docker 网络相关的问题你基本都能自己定位。

网络模式总览:先建立整体概念#

容器启动时必须选一种网络模式(--network),它决定了容器和外界、容器和容器之间怎么通信。Docker 内置了下面几种驱动,先看总览表,后面逐个展开:

模式原理优点缺点适用场景
bridge(默认)容器接入虚拟网桥 docker0,各自有独立网段内的 IP,靠 NAT 出网、靠端口映射入网隔离性好、最通用多一层 NAT,性能略损;默认网络不支持容器名解析绝大多数单机应用
bridge(自定义)原理同上,但网段、网关可控,且自带容器名 DNS 解析容器可用名字互访、网络可隔离分组需要自己规划网段多容器项目(Compose 默认就这么做)
host容器直接共用宿主机网络命名空间,没有自己的网卡和 IP性能最好、无端口映射概念端口直接占用宿主机、隔离性差;仅 Linux 可用对网络性能极敏感、或需要监听大量端口的服务
none只有回环网卡 lo,完全断网绝对隔离不能联网只做本地计算、批处理类容器
container与另一个已有容器共用网络命名空间两个容器像同一台机器,用 localhost 互访命运绑定,被共享的容器重启会受影响伴生容器,如 VPN 容器 + 借道走它的应用
macvlan给容器分配局域网真实 IP 和独立 MAC,像一台独立设备接入物理网络容器在局域网里就是一台「真机」配置复杂;默认与宿主机不通(需额外处理)需要容器直接出现在局域网、接管特定 IP 的场景

选型口诀:单机多容器用自定义 bridge(Compose 默认),追求性能或特殊需求再考虑 host,局域网独立 IP 才上 macvlan,none 和 container 是小众特殊场景。 下面按重要程度展开。

bridge 模式与端口映射#

默认 bridge 是什么样#

不加任何网络参数时,容器都接入默认的 docker0 网桥,网段通常是 172.17.0.0/16,容器 IP 从 172.17.0.2 开始顺序分配。容器访问外网靠宿主机的 NAT 转发,没有任何问题;但外界想访问容器里的服务,就必须做端口映射,把宿主机端口和容器端口绑定。

先看一下你机器上的网络现状:

Terminal window
docker network ls
NETWORK ID NAME DRIVER SCOPE
a1b2c3d4e5f6 bridge bridge local
b2c3d4e5f6a7 host host local
c3d4e5f6a7b8 none null local

bridge、host、none 三个是 Docker 自带的,不能删除。

端口映射的写法#

docker run 用 -p,完整格式是:

-p [宿主机IP:]宿主机端口:容器端口[/协议]

常见写法:

Terminal window
# 宿主机 8080 → 容器 80,所有网卡可访问(等价于 0.0.0.0:8080)
docker run -d -p 8080:80 nginx
# 只绑本机回环:只有宿主机自己能访问,外网连不进来
docker run -d -p 127.0.0.1:8080:80 nginx
# UDP 端口要显式带 /udp(比如 DNS、游戏服务器、HY2 类服务)
docker run -d -p 53:53/udp mydns
# 一次映射多个
docker run -d -p 8080:80 -p 8443:443 nginx

Compose 里对应的是 ports,写法一致,记得加引号,避免 YAML 把 8080:80 这类值误解析:

services:
nginx:
image: nginx:latest
ports:
- "8080:80"
- "127.0.0.1:8443:443"

查看某个容器实际映射了哪些端口:

Terminal window
docker port <容器名>
docker ps # PORTS 列也能直接看到

只绑 127.0.0.1:最容易被忽视的安全习惯#

-p 8080:80 默认绑的是 0.0.0.0,也就是全网可达:只要云厂商安全组或防火墙放行,任何人都能连上这个端口。很多数据库、面板类容器出事,就是因为图省事直接把端口暴露到了公网。

如果这个服务只需要给本机其他程序用,或者前面还有 Nginx 反向代理在转发,就应该只绑回环地址:

Terminal window
docker run -d -p 127.0.0.1:3306:3306 mysql

这样即使防火墙全开,外网也连不进来,本机通过 127.0.0.1:3306 正常访问。判断原则很简单:这个端口需要被外网直接访问吗?不需要,就绑 127.0.0.1。 数据库、Redis、管理面板,统统属于「不需要」。

WARNING

Docker 的端口映射是直接写 iptables/nftables 规则的,会绕过 ufw 的默认拦截策略。也就是说你在 ufw 里没放行,不代表 Docker 映射的端口外网连不上——这正是「只绑 127.0.0.1」如此重要的原因。不要以为装了 ufw 就万事大吉。

userland-proxy=false 是什么意思#

Docker 做端口映射有两种实现:一种是内核层的 NAT 规则转发;另一种是每个映射端口再起一个用户态代理进程 docker-proxy 做转发。/etc/docker/daemon.json 里加上:

{
"userland-proxy": false
}

就是关掉那个用户态代理,全部走内核 NAT。好处是少一层转发、性能略好、进程列表更干净,这也是很多长期使用 Docker 的人的固定偏好。代价要知道两个:

  • 关掉后,从容器内通过「宿主机 IP + 映射端口」回访自己映射出去的端口可能不通(hairpin 场景),容器间通信请走后面讲的自定义网络,不要绕宿主机端口;
  • 某些依赖代理进程保留源 IP 细节的边缘场景行为会变化,普通使用无感。

改完 daemon.json 必须重启 Docker 才生效:

Terminal window
systemctl restart docker

自定义 bridge 网络:容器互访的正解#

默认 bridge 的两个硬伤#

默认 bridge 能用,但有两个先天不足:

  1. 不支持容器名解析。在默认网络里,容器 A 想访问容器 B,只能用 B 的内网 IP(如 172.17.0.3),而 IP 是重启后重新分配的,写死了迟早出事。老旧的 --link 方案早已被官方废弃,不要再用。
  2. 所有容器挤在一个网络里,互相之间毫无隔离,A 项目的容器能直接访问 B 项目的数据库。

自定义 bridge 网络把这两个问题都解决了:它内置了一个 DNS 服务器(地址 127.0.0.11),同一网络内的容器可以直接用容器名/服务名互相访问,IP 变了也不怕;不同项目用不同网络,天然隔离。

手动创建与使用#

Terminal window
# 创建一个自定义网络,可指定网段和网关
docker network create \
--driver bridge \
--subnet 172.20.0.0/16 \
--gateway 172.20.0.1 \
mynet
# 两个容器接入同一网络
docker run -d --name web --network mynet nginx
docker run -d --name app --network mynet myapp
# 在 app 容器里直接用名字访问 web
docker exec app curl http://web

规划网段时避开你局域网和常用 VPN 的网段(比如家里路由器是 192.168.1.0/24 就别用它),Docker 默认分配的 172.16.0.0/12 私有段一般不冲突。

动态接入与固定 IP#

容器跑起来之后也能变更网络归属,不用重建:

Terminal window
docker network connect mynet <已存在的容器> # 把运行中的容器接入某网络
docker network disconnect mynet <容器> # 从某网络断开

一个容器可以同时接入多个网络(比如前面的 app 同时在 frontend 和 backend 里),connect 就是实现手段。Compose 里增删 networks 后 up -d 会自动完成同样的事,手动命令主要用在临时调试。

有些场景需要给容器固定内网 IP(比如防火墙规则按 IP 放行、或老应用写死了对端地址),自定义网络支持指定:

Terminal window
docker run -d --network mynet --ip 172.20.0.10 nginx

Compose 写法:

services:
db:
image: postgres:16
networks:
backend:
ipv4_address: 172.21.0.10
networks:
backend:
ipam:
config:
- subnet: 172.21.0.0/16

注意固定 IP 只能用在自己规划了子网的自定义网络上,默认 bridge 不支持。没特殊需求时不要滥用固定 IP——名字解析已经解决了寻址问题,固定 IP 反而增加维护负担和冲突风险。

Compose 里怎么配#

用 Compose 时其实你已经在用自定义网络了:Compose 会为每个项目自动创建一个名为 <项目名>_default 的网络,同一 compose 文件里的服务默认全部接入,彼此用服务名互访。比如上一篇的例子里,Nginx 容器想连 Redis,直接写 redis:6379 就行,不需要任何额外配置。

需要显式规划时这样写:

services:
web:
image: nginx:latest
networks:
- frontend
app:
image: myapp:latest
networks:
- frontend
- backend
db:
image: postgres:16
networks:
- backend
networks:
frontend:
backend:

这个例子里 web 和 db 不在同一网络,web 无法直连数据库,必须经过 app 中转——这就是用网络做访问隔离的标准手法,比在应用层堵要可靠得多。

WARNING

容器互访用的是容器端口,不是映射到宿主机的端口。上例中 app 访问 db 写的是 db:5432(PostgreSQL 的容器端口),与 db 有没有在 ports 里映射、映射成多少,都没有关系。把这两个概念搞混是新手最常见的互访故障来源。

host 模式实战与注意事项#

host 模式下容器没有自己的网络空间,直接用宿主机的网卡、IP 和端口。启动方式:

Terminal window
docker run -d --network host nginx

此时 Nginx 监听的 80 端口就是宿主机的 80 端口,访问 http://宿主机IP 直达,没有映射这一步。Compose 写法:

services:
nginx:
image: nginx:latest
network_mode: host

注意事项必须一条条说清:

  • ports 配置会失效。host 模式下写 ports 不仅没用,Compose 还会警告并忽略它。端口冲突直接发生在宿主机层面:宿主机 80 已被占用,容器就起不来。
  • 仅 Linux 可用。Docker Desktop(Mac/Windows)里的 host 模式行为完全不同,不要在那上面验证生产配置。
  • 隔离性基本归零。容器进程能看到宿主机全部网络接口,还能抓到本机流量,只对完全可信的镜像使用。
  • 适合它的场景:需要监听成百上千个端口、做软路由/网关类应用、对 NAT 那一点性能损耗都敏感的高吞吐服务。普通 Web 应用用 bridge 完全够了,不要跟风上 host。

macvlan 补充:与宿主机不通是设计如此#

前面表格里说 macvlan 容器像局域网里的一台独立设备,这里补一个最常踩的点:macvlan 容器默认和宿主机互相 ping 不通。这是内核出于安全隔离的设计(同一个物理网卡上的 macvlan 端点与宿主机端点被隔开),不是配置错误。如果业务上需要宿主机访问 macvlan 容器,要在宿主机上再建一个 macvlan 子接口并配上同网段地址、加一条主机路由,操作偏繁琐。所以选型时要想清楚:只是想要「容器有个固定内网地址」,自定义 bridge + 固定 IP 就够了;只有当路由器、局域网其他设备必须把容器当独立机器发现和访问时,才值得上 macvlan。

创建 macvlan 网络时要把局域网的网段、网关和物理网卡名都写对:

Terminal window
docker network create -d macvlan \
--subnet 192.168.1.0/24 \
--gateway 192.168.1.1 \
-o parent=eth0 \
lan-net

其中 parent 是宿主机真实网卡名(用 ip link 查看),写错整个网络不可用;分配给容器的 IP 也要避开路由器 DHCP 的地址池范围,防止和局域网其他设备撞 IP。

IPv6 完整开启方法#

Docker 默认只开 IPv4。宿主机本身有 IPv6 的话,把容器网络的 IPv6 打开只要三步。

第一步:改 daemon.json#

{
"ipv6": true,
"fixed-cidr-v6": "fdb::/64"
}

说明两点:

  • ipv6: true 是总开关,同时 Docker 会启用 ip6tables 规则管理;
  • fixed-cidr-v6 是给默认 bridge 分配的 IPv6 网段。这里用 fdb::/64 属于 ULA 私有地址段(fd00::/8),只在内部使用、不参与公网路由,不会和任何公网地址冲突,是固定写法的稳妥选择。不要把宿主机的公网 IPv6 网段直接填进来硬套,不懂路由规划时容易把网络搞乱。

第二步:重启 Docker 生效#

Terminal window
systemctl restart docker
docker network inspect bridge | grep -i ipv6
WARNING

重启 Docker 会重启所有容器(配了 live-restore 的除外,见第一篇)。生产机器上请挑低峰期操作,并提前确认各容器都配了 restart: unless-stopped 之类的自启策略。

第三步:Compose 项目里开启#

全局开了 IPv6 后,Compose 的自定义网络还要单独声明才有 IPv6:

services:
web:
image: nginx:latest
ports:
- "8080:80"
networks:
default:
enable_ipv6: true
ipam:
config:
- subnet: "fdb:1::/64"

每个项目给一个不同的 ULA 子网(如 fdb:1::/64、fdb:2::/64),避免网段重叠。

验证方法#

Terminal window
# 1. 看网络里是否已启用 IPv6 并分到了网段
docker network inspect <网络名>
# 2. 进容器看网卡,应该能看到 inet6 地址
docker exec web ip addr
# 3. 容器内测 IPv6 连通性(容器需装 iputils,debian 系 apt install iputils-ping)
docker exec web ping6 -c 3 fdb::1
# 4. 从宿主机按 IPv6 访问映射端口(如映射时绑了 v6)
curl -g -6 "http://[::1]:8080"

如果第 2 步容器里根本没有 inet6 地址,先回头检查 daemon.json 语法(JSON 不能有注释和多余逗号)和 Docker 是否真的重启成功:systemctl status docker 看启动时间。

WARNING

容器有 IPv6 地址 ≠ 能用 IPv6 上公网。ULA 地址只保证内网互通;要让容器借宿主机的公网 IPv6 出网,还需要宿主机开启转发并做 NDP 代理或前缀委派,属于进阶路由话题,不要指望改个 daemon.json 就自动通公网。

排障工具箱#

网络问题按「先确认事实,再缩小范围」的顺序查,下面这套命令能覆盖九成场景。

常用命令速查#

Terminal window
docker network ls # 有哪些网络
docker network inspect <网络名> # 看网段、网关、接入了哪些容器及其 IP
docker inspect <容器名> # 看某个容器在各网络中的 IP、别名
docker exec <容器名> ip addr # 容器自己的网卡视角
docker exec <容器名> cat /etc/resolv.conf # 容器用的 DNS(自定义网络应为 127.0.0.11)
ss -tlnp # 宿主机上到底谁在监听哪些端口
docker port <容器名> # 映射关系一览

场景一:端口映射了,但外网访问不了#

按这个顺序逐项排除:

  1. 宿主机本地先测:curl http://127.0.0.1:8080,通不通决定了问题在 Docker 还是在网络层;
  2. 本地通、外网不通 → 九成是云厂商安全组没放行或机房防火墙拦截,去控制台放行对应端口;
  3. 看映射是不是只绑了 127.0.0.1(docker port 输出带 127.0.0.1 前缀),是的话外网本来就进不来,属预期行为;
  4. ss -tlnp | grep 8080 确认端口确实在监听、且监听进程是 docker。

场景二:两个容器互访不通#

  1. 先确认在不在同一个网络:docker network inspect <网络名> 看 Containers 列表里有没有双方;
  2. 不在一个网络,要么把它们接进同一网络,要么明确接受不通(隔离设计);
  3. 在同一网络还不通,检查你用的是容器名+容器端口,而不是宿主机映射端口;
  4. 在默认 bridge 网络里用容器名互访必然失败——默认网络没有名字解析,改用自定义网络,这是设计如此,不是故障。
Terminal window
# 容器内连通性三连测
docker exec app ping -c 3 db # 名字能否解析、网络通不通
docker exec app curl -v http://db:5432 # 端口层面的确认(换成目标服务端口)

场景三:启动报错端口被占用#

报错形如 Bind for 0.0.0.0:8080 failed: port is already allocated:

Terminal window
ss -tlnp | grep 8080 # 先看占用者是谁:可能是另一个容器,也可能是宿主机原生进程
docker ps # 若是另一个容器占的,改它或改自己的宿主机端口

处理原则:改宿主机侧端口即可(如 8080→8081),容器端口不要动,应用配置也不用改。

场景四:容器里上不了网#

Terminal window
docker exec app ping -c 3 223.5.5.5 # 先测纯 IP,排除 DNS 因素
docker exec app ping -c 3 baidu.com # 再测域名

IP 通、域名不通是 DNS 问题(检查容器 resolv.conf 与 daemon.json 的 dns 配置);IP 也不通,多半是宿主机转发没开或防火墙 FORWARD 链被其他软件改过,先查 sysctl net.ipv4.ip_forward 应为 1,再回忆最近是否装过其他网络类工具动过 iptables。

场景五:同网络内名字解析失败#

在自定义网络里用容器名 ping 不通、报 Name or service not known,按下面查:

  1. 确认两个容器真的在同一个网络,且用的是容器名或 Compose 服务名,不是镜像名;
  2. 进容器看 /etc/resolv.conf,自定义网络的 nameserver 应该是 127.0.0.11(Docker 内置 DNS),若被镜像改成了别的,先解决镜像的 DNS 配置;
  3. Compose 里给服务改过 container_name 的,服务名和容器名都能解析;若还用了网络别名(aliases),别名同样有效,可在 docker network inspect 输出里核对每个容器的 Aliases 字段;
  4. 宿主机或公司内网有 mDNS、systemd-resolved 劫持时偶尔会干扰解析结果,用 docker exec app getent hosts db 看容器内部的真实解析结果,别在宿主机上测了下结论。

小结#

  • 网络模式选型:默认用自定义 bridge,host 只给性能敏感且可信的服务,macvlan 只在需要局域网真实 IP 时用;
  • 端口映射默认全网可达,不需要外网直连的端口一律只绑 127.0.0.1,且记住 Docker 映射会绕过 ufw;
  • 容器互访的正解是同一自定义网络 + 服务名 + 容器端口,默认 bridge 没有名字解析,别再用 IP 硬编码和 --link;
  • IPv6 开启就三步:daemon.json 写 ipv6: true 与 fixed-cidr-v6: "fdb::/64"、重启 Docker、Compose 网络里再声明 enable_ipv6,验证用 inspect + 容器内 ip addr/ping6;
  • 排障先分清层次:映射问题查 docker port 和 ss,互访问题查是否同网络,通不通外网先分清本地与云安全组。

网络这块吃透了,后面讲反向代理、监控这些实战部署时就不会再被「连不上」卡住。

Docker 网络详解:从 bridge 到 IPv6
https://blog.anexns.com/posts/docker-network/
作者
Anexns
发布于
2026-11-15
许可协议
CC BY-NC-SA 4.0