UFW 导致 Docker 容器无法使用 IP 访问宿主机

问题描述

  • Debian 服务器的内网 IP(宿主机 IP) 是 192.16.1.5,启用了 UFW 防火墙,并且服务器上运行着两个 Docker 容器。
  • 假设用 -p 8080:80 参数启动了容器 A 内的服务,在容器 B 内的服务通过宿主机 IP(192.16.1.5:8080)访问容器 A 的服务,发现无法正常访问。

问题分析

Debian 启用 UFW 防火墙后,Docker 容器可能无法使用宿主机的 IP 地址相互通信。这是因为 Docker 默认会直接修改 iptables 规则,而这些规则可能与 UFW 的配置产生冲突,导致通信受阻。

问题解决

解决方案一

关闭 UFW 防火墙,这是最简单的方案,但也是最危险的方案。因为服务器将失去 UFW 防火墙的保护,容易受到恶意攻击。

解决方法二

自定义 Docker 桥接网络,并添加 UFW 防火墙规则允许 Docker 桥接网络与宿主机互相通信。

  • 定义 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
services:

user-biz:
image: clay/user-biz:latest
container_name: user-biz
restart: always
ports:
- 8080:80
networks:
cloud_default:
ipv4_address: 173.18.0.05

order-biz:
image: clay/order-biz:latest
container_name: order-biz
restart: always
ports:
- 9090:80
networks:
cloud_default:
ipv4_address: 173.18.0.06

networks:
cloud_default:
name: cloud_default
driver: bridge
ipam:
config:
- subnet: 173.18.0.0/16
  • 或者手动创建 Docker 桥接网络,同时指定自定义网络的子网范围,并在容器启动时指定桥接网络
1
sudo docker network create --driver bridge --subnet 173.18.0.0/16 cloud_default
1
2
sudo docker run -d --name user-biz -p 8080:80 --network cloud_default --ip 173.18.0.05 clay/user-biz:latest
sudo docker run -d --name order-biz -p 9090:80 --network cloud_default --ip 173.18.0.06 clay/order-biz:latest
  • 查看 Docker 桥接网络的 IP 分配情况
1
sudo docker inspect network cloud_default
1
2
3
4
5
6
7
8
9
10
11
12
13
14
"d268549f1335d1078c3692e1208a0376eb403a920a176642983f2eb0f64f4b61": {
"Name": "user-biz",
"EndpointID": "8d5cba4fe76a69b4169e6266ca2d3a5cbd9be84fdcf0e7f4f3ab6a05c2825a1f",
"MacAddress": "8e:40:19:99:af:49",
"IPv4Address": "173.18.0.05/16",
"IPv6Address": ""
},
"f83453e8d515e58a1c5a68c7957835ed82cece7104129dba1fcb604bb1215d8a": {
"Name": "order-biz",
"EndpointID": "8899737f0c54cfb3f4fac0f9effaeaf318eb12e8d0d4988fff399a2d94e9be0b",
"MacAddress": "72:6e:1b:a8:15:26",
"IPv4Address": "173.18.0.06/16",
"IPv6Address": ""
}
  • 添加 UFW 防火墙规则(使用 CIDR 表示法),允许 Docker 桥接网络与宿主机互相通信。
1
2
sudo ufw allow from 173.18.0.0/16
sudo ufw allow out to 173.18.0.0/16

补充说明

提示

Docker 容器内部访问宿主机,还可以使用 host.docker.internal 来实现(对系统有要求)。

在 Docker 容器化开发中,一个经典难题是:容器内如何访问宿主机上的服务(如本地数据库、缓存或 API)?host.docker.internal 是 Docker 官方为此提供的一个特殊 DNS 域名,它专门用于在容器内部解析并指向宿主机的 IP 地址

基本用法

docker run 命令中:

1
docker run --add-host host.docker.internal:host-gateway your-image

或者在 Docker Compose 文件中(docker-compose.yml):

1
2
3
4
5
services:
app:
image: your-app
extra_hosts:
- "host.docker.internal:host-gateway"

配置完成后,容器内的应用程序就可以通过 host.docker.internal 这个域名来访问宿主机上的服务了。

示例: 在 Java 应用中连接宿主机上的 MySQL:

1
String url = "jdbc:mysql://host.docker.internal:3306/mydb";

使用限制

使用 host.docker.internal 时需要清楚以下限制:

限制点说明
平台支持Docker Desktop(macOS / Windows) 默认原生支持。在 Linux 上,Docker Engine 不内置该域名(较新版本可能内置支持),需要手动指定宿主机 IP(如 172.17.0.1)。
仅适用于开发环境该域名设计初衷是简化本地开发不应在生产环境使用。生产环境应使用服务发现、环境变量或容器编排(如 Kubernetes)来处理网络通信。
容器网络模式影响仅在默认的 bridge 网络模式或自定义桥接网络中有效。如果使用 host 网络模式(network_mode: host),容器与宿主机共享网络栈,无需此域名。
端口绑定限制宿主机上的服务必须监听在 0.0.0.0(所有接口),而不能只监听 127.0.0.1,否则容器通过 host.docker.internal 无法访问。

注意事项

(1) Linux 系统不生效怎么办?

在 Linux 上,最常用的替代方案是直接使用宿主机在 Docker 桥接网络中的网关 IP,通常为 172.17.0.1。或者,你也可以在 Linux 下手动添加 hosts 映射:

1
docker run --add-host host.docker.internal:172.17.0.1 your-image

提示:不同 Linux 发行版或自定义网桥可能导致网关 IP 变化,建议用 docker network inspect bridge 确认。


(2) 检查宿主机服务监听地址

这是最容易踩的坑。假设你在宿主机启动了一个 Redis:

1
redis-server --bind 127.0.0.1  # ❌ 容器连不上

必须改为:

1
redis-server --bind 0.0.0.0   # ✅ 容器能访问

或者修改配置文件,确保服务监听所有接口。


(3) 安全提醒

host.docker.internal 本质上是在容器内暴露了宿主机的网络入口。如果容器内的应用存在漏洞(如 SSRF),攻击者可能通过该域名访问宿主机上的敏感服务。因此在开发环境中也应避免运行不必要的敏感服务,或使用防火墙做限制。


(4) Docker 版本与弃用风险

该特性在 Docker 18.03+ 引入并稳定。Docker Desktop 文档中已将其列为稳定功能,目前无弃用计划,但依赖底层网络实现,未来版本可能有行为调整,建议关注 Docker 官方发布说明。

总结说明

场景建议
macOS / Windows 本地开发优先使用 host.docker.internal
Linux 本地开发使用 172.17.0.1 或手动添加 hosts
生产环境禁止使用,改用服务名(如 Kubernetes Service)或外部配置中心
访问宿主机端口确保宿主机服务监听 0.0.0.0

参考资料