明明用 UFW 防火墙限制了端口,Docker 映射的端口却仍然能从公网访问?这不是 Bug,而是 Docker 的工作原理导致的。本文将深入分析原因,并提供 6 种切实可行的解决方案。

一、问题的根源:Docker 与 iptables
当你使用 docker run -p 8080:80 nginx 这样的命令时,Docker 会默默地在宿主机的 iptables 规则中插入端口转发规则。这本身是为了让容器端口能正常工作——它将宿主机 8080 端口的流量转发到容器的 80 端口,并让容器能访问外部网络。
问题的关键在于:Docker 写入的 iptables 规则优先级高于 UFW。UFW(Uncomplicated Firewall)本质上是 iptables 的前端管理工具,它维护自己的规则链。但 Docker 直接操作的是 iptables 的 NAT 表和 FILTER 表,将规则插入到 UFW 规则链的更前面。更令人迷惑的是,这些由 Docker 添加的规则在 ufw status 命令中完全不可见。
这意味着即使你在 UFW 中显式拒绝了 8080 端口,Docker 的 iptables 规则仍然会让该端口对公网开放。使用 nmap 扫描你的服务器公网 IP 就会发现,端口是开放的。
二、6 种解决方案详解

方案一:禁用 Docker iptables(不推荐)
在 /etc/docker/daemon.json 中设置 {"iptables": false}。虽然能解决端口暴露问题,但会导致容器无法访问外部网络,且容器间的通信也会失败。这个方案相当于因噎废食,强烈不推荐。
方案二:云服务商安全组(推荐)
在阿里云、腾讯云、GCP 等云服务商的后台安全组/防火墙规则中直接禁用端口。由于安全组是在云平台底层实现的,Docker 的 iptables 规则无法绕过。这是最省心的方案。但缺点是部分小厂商的服务器没有安全组功能。
方案三:监听 127.0.0.1(强烈推荐单机场景)
端口映射时使用 docker run -p 127.0.0.1:8080:80 nginx 而非 -p 0.0.0.0:8080:80。这样容器端口仅监听在本地回环地址上,外部完全不可达。本机的其他 Web 应用可以通过 Nginx 反向代理来访问这些容器。这是单机场景下最优雅的方案。

如果另一台服务器需要反代这台机器上的容器,方案三就有限制了。此时可以结合方案五来解决。
方案四:host 网络模式(有条件使用)
使用 --network host 让容器共享宿主机的网络栈,此时可以使用 UFW 来控制端口。缺点是容器之间没有网络隔离,且多个容器无法同时使用同一个端口(如 80 端口)。
方案五:虚拟局域网(推荐多服务器场景)
使用 Tailscale 或 ZeroTier 组建虚拟局域网。在端口映射时同时绑定 127.0.0.1 和虚拟局域网 IP,另一台服务器通过 VPN 地址来反代,既安全又灵活。
方案六:ufw-docker(强烈推荐)
ufw-docker 是一个开源方案,通过在 /etc/ufw/after.rules 中添加 DOCKER-USER 链规则,让 UFW 能够控制 Docker 容器的端口。安装后可以使用 ufw route allow 精确放行指定容器端口或指定对端 IP。也提供一键安装工具 ufw-docker install。

三、方案选择建议
| 场景 | 推荐方案 |
|---|---|
| 单机使用 | 方案三:127.0.0.1 + Nginx 反代 |
| 云服务器 | 方案二:云安全组 |
| 多机反代 | 方案六:ufw-docker |
| 多机 + 安全 | 方案五:虚拟局域网 |
千万不要直接禁用 Docker 的 iptables 管理,这会导致容器网络功能完全失效。
原创文章,作者:kp51,如若转载,请注明出处:https://www.kepu51.com/instant-messaging/701.html
