结论先说:90% 的场景用自定义 bridge 网络就够了,需要容器像物理机一样出现在局域网时才上 macvlan,host 模式只留给性能极度敏感的少数服务;而最危险的坑是 Docker 的端口映射直接写 iptables,绕过了 UFW,你放行规则里没开的端口照样裸奔公网。 我在 2026 年 10 月给一台 Ubuntu 22.04(Docker 27)的机器做安全加固时才真正把这事想明白——之前一直以为 UFW 管着一切,后来发现容器映射的端口在 UFW 眼里根本不存在。这篇按「原理—实操—排错」的顺序,把三种模式讲透。

bridge、host、macvlan 到底差在哪?
三种模式的核心区别是容器用谁的 IP 出现在网络上:
| 维度 | bridge(默认) | host | macvlan |
|---|---|---|---|
| 容器 IP | 网桥内网 IP(172.x) | 直接用宿主 IP | 独立局域网 IP(192.168.x) |
| 端口暴露 | 需 -p 映射 | 不需要,直接监听 | 不需要,天然可达 |
| 容器间互访 | 同网络内按服务名互访 | 按宿主端口区分 | 局域网内直接互访 |
| 性能 | 经过 NAT,损耗 5-10% | 最好,零转换 | 接近物理机 |
| 隔离性 | 最好 | 最差 | 中 |
| 典型场景 | Web 服务、compose 全家桶 | 高性能代理、采集器 | NAS 工具、需要固定 IP 的设备 |
一句话记忆:bridge 是「隔离小区+统一门牌」,host 是「住进宿主家里」,macvlan 是「给容器发一张独立门牌」。 日常先 bridge,有特殊理由才换,这是不变的原则。
三种模式怎么实操?
环境:Ubuntu 22.04 + Docker 27。四步走:自建 bridge、跑一个 host 服务、配 macvlan、最后是排错命令。

第一步:自建 bridge 网络并让容器互访
默认的 bridge0 网络不支持容器名互访(只能 IP),这是默认网络和自定义网络最大的区别,也是很多人「容器之间 ping 不通」的原因:
$ docker network create appnet
$ docker run -d --name web --network appnet nginx:1.27
$ docker run -d --name db --network appnet -e POSTGRES_PASSWORD=xxx postgres:16
$ docker exec web ping -c 2 db
PING db (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.088 ms
--network appnet 之后,db 这个名字就是可解析的 DNS 记录。而 Docker Compose 默认会为每个项目创建自定义网络,所以 compose 里服务名互访天然可用——很多人不知道这个便利其实是「自定义网络」给的,手动 docker run 时就露馅了。

第二步:host 模式跑对性能敏感的服务
$ docker run -d --network host --name node-exporter prom/node-exporter:v1.8.2
$ ss -tlnp | grep 9100
LISTEN 0 4096 *:9100 users:(("node-exporter",pid=1234,fd=3))
host 模式下容器直接用宿主的网络栈,没有 NAT 转换,-p 参数无效也不需要。我把它用在两类场景:一是 Prometheus 监控的 node_exporter——它要看宿主的真实网络指标,host 模式天然合适;二是流量大的代理转发类服务,省掉一层 NAT。风险也要说透:容器监听的任何端口都直接暴露在宿主上,绕过了端口映射这一层隔离,host 模式里跑的镜像必须信得过,且最好配合 --read-only 等加固参数。

第三步:macvlan 给容器独立局域网 IP
场景:家里软路由上跑一个 NAS 工具,希望它像一台真机器一样出现在 192.168.1.x 网段,手机直接访问它的 IP,不经过任何端口映射:
networks:
lan:
driver: macvlan
driver_opts:
parent: eth0
ipam:
config:
- subnet: 192.168.1.0/24
gateway: 192.168.1.1
services:
nas-tool:
image: your-nas-tool:latest
networks:
lan:
ipv4_address: 192.168.1.50
parent: eth0 指定容器流量从宿主哪块物理网卡出去,ipv4_address 必须是局域网里没被占用的地址(建议在路由器上做 DHCP 保留)。起来之后 ping 192.168.1.50 从局域网任何设备都能通。
但这里有一个经典反转:我一开始以为容器有了独立 IP 就万事大吉,后来发现宿主机自己反而 ping 不通这个容器了——宿主和 macvlan 容器在内核里是两条隔离的链路,这是 macvlan 的设计限制。 解法是在宿主上建一个「垫片」接口:
$ ip link add macvlan-shim link eth0 type macvlan mode bridge
$ ip addr add 192.168.1.240/32 dev macvlan-shim
$ ip link set macvlan-shim up
$ ip route add 192.168.1.50/32 dev macvlan-shim
建一次永久生效需要写进 netplan 或 systemd unit。垫片接口让宿主通过它「挂到」同一个 macvlan 上,宿主与容器之间就通了。

第四步:排查命令清单
日常排错四条命令覆盖 90% 的问题:
$ docker network inspect appnet
# 看成员列表、网段、网关——「容器为什么互访不通」先查它
$ docker exec web ping -c 2 db
# 容器间连通性 + DNS 解析
$ docker exec web wget -qO- http://db:5432
# 端口级验证,ping 通不代表服务端口通
$ iptables -L DOCKER -n | grep 8080
# 查 Docker 写进内核的端口转发规则
docker network inspect 的输出里我最常看 Containers 段——容器到底在不在网络里、IP 是多少,一眼定案。

为什么容器端口绕过了 UFW?
根因:Docker 的 -p 端口映射不走 UFW 的规则链,而是直接在 iptables 的 DOCKER 链里插入转发规则,而这条链的优先级在 UFW 之前。 所以 ufw deny 6379 挡不住 -p 6379:6379 的 Redis——外部流量在 UFW 看到之前就已经被 DOCKER 链放行了。这就是那台加固机器上扫出来的问题:UFW 显示只开了 22/80/443,实际 nmap 能扫到七八个容器端口。
解法按优先级:
- 映射时只绑本机回环:
-p 127.0.0.1:6379:6379,需要公网访问的交给反向代理统一入口,这是最干净的办法。 - 用 DOCKER-USER 链过滤:Docker 官方预留了这个链,用户规则写在这里:
$ iptables -I DOCKER-USER -p tcp --dport 6379 ! -s 10.0.0.0/8 -j DROP
- 装 ufw-docker 工具(github 上 gawel/ufw-docker),它帮你把 UFW 和 Docker 的规则协调好,省得手写 iptables。
千万不要用 "iptables": false 关掉 Docker 的 iptables 管理——这会导致容器出站转发全断,属于拆承重墙式解法。详细的规则模板在UFW + Docker 防火墙实战里写过,不重复。
怎么选,一张速查表
需求是「几个 Web 服务互相调用」——bridge + compose,完事。需求是「容器要在局域网当一台独立机器」——macvlan + 垫片接口。需求是「榨干性能或要看宿主网络栈」——host,同时接受它的隔离代价。至于某些教程鼓吹的「全部用 host 最快」,单机跑两三个容器的场景下 NAT 损耗根本感知不到,为这点性能放弃隔离不划算。如果容器网络通了但服务还是从公网访问不到,多半是没有公网 IP 的网络环境,那是另一个问题,用 FRP 或 Cloudflare Tunnel 解决。
常见问题
Q:compose 项目之间能互访吗?
A:默认不能,每个项目有独立网络。让它们加入同一个 external: true 的自定义网络即可,服务名照常互访,这是多 compose 项目协作的标准做法。
Q:macvlan 容器能上外网吗?
A:能。网关指向路由器,出站走正常路由。要注意的只是上面说的宿主互访问题,不是外网问题。
Q:host 模式下容器还能用 docker port 查端口吗?
A:不能,docker port 输出为空,因为不存在映射。用 ss -tlnp 在宿主上看,进程名就是容器内的进程。
Q:怎么确认某个端口到底有没有被 UFW 挡住?
A:别看 UFW 的规则输出,直接从外部机器 nmap -p 6379 你的服务器IP,或者 nc -zv。UFW 说没开但外部能连,就是 Docker 绕过链的现场。
跨主机的场景这篇没展开:单机的三种模式选完,多台机器之间的容器通信要走 overlay 网络或者干脆用 WireGuard 组网篇的思路把几台 VPS 串成私网——后者是我目前的主用方案,配置量小、无额外组件。最后留一张排错速查:容器互 ping 不通用 docker network inspect 看是否同网;端口映射不通用 docker port 核对;宿主机访问不到容器先查 UFW 的 DOCKER-USER 链。网络问题九成出在这三处。