判断 Docker 端口是否暴露,需要分别查看容器内监听地址、宿主机端口绑定,以及防火墙和云安全组。容器内监听 0.0.0.0,不等于该端口已经暴露到公网。
监听地址、宿主绑定和服务名属于不同网络层
| 配置位置 | 示例 | 含义 |
|---|---|---|
| 应用进程监听 | 0.0.0.0:8080 | 接受当前容器所有网络接口上的 8080 连接 |
| 应用进程监听 | 127.0.0.1:8080 | 只接受同一容器回环接口的连接 |
| Docker 端口发布 | 127.0.0.1:8080:8080 | 宿主机本地可通过 8080 访问容器 8080 |
| Docker 端口发布 | 0.0.0.0:8080:8080 | 宿主机全部 IPv4 接口发布 8080,可能被公网访问 |
| Compose 内部访问 | http://app:8080 | 同网络服务通过服务名访问容器端口 |
两处 0.0.0.0 的作用不同。应用在容器内监听全部接口,是为了让同网络代理访问;Docker 把端口发布到宿主全部接口,则扩大了宿主机入口。
表中回环绑定的结论以 Docker Engine 28.0.0 及以上、默认 NAT 网桥为前提。更旧版本存在同一二层网络中的其他主机仍能访问 localhost 发布端口的问题;启用了 direct routing 或 routed gateway mode 时,还要检查容器地址是否能被外部路由到。升级 Docker 后仍应从外部网络复核敏感端口。
反向代理和应用都在 Compose 中
只有代理需要发布公网端口,应用和数据库不需要 ports:
services:
proxy:
image: caddy:2
ports:
- "80:80"
- "443:443"
- "443:443/udp"
networks: [web]
app:
image: example/app:1.4.2
expose:
- "8080"
networks: [web, data]
postgres:
image: postgres:18-alpine
expose:
- "5432"
networks: [data]
networks:
web:
data:
internal: true
expose 用于声明和记录容器端口,不会发布到宿主机。即使省略,同一网络中的服务仍可直接访问容器实际监听端口。代理使用 app:8080,数据库只加入 data 网络,可以减少不必要的横向可达关系。
应用进程仍要监听容器内的 0.0.0.0:8080。如果只监听 127.0.0.1,proxy 容器到 app 容器的连接会被拒绝。
代理运行在宿主机时用回环发布
Caddy 或 Nginx 作为宿主机 systemd 服务运行时,可以把应用端口只发布到宿主回环:
services:
app:
image: example/app:1.4.2
ports:
- "127.0.0.1:8080:8080"
宿主代理连接 127.0.0.1:8080,公网只能看到代理公开的 80/443。不要为了方便调试改成 8080:8080 后忘记收回;未指定宿主地址时,Docker 默认会发布到所有宿主地址。
Docker 端口发布文档(在新标签页打开)明确提示,发布端口默认对外可用,属于安全敏感操作。
防火墙和云安全组只开放必要端口
典型公开网站的公网端口通常只有:
- 80/tcp:HTTP 跳转和 HTTP-01 证书验证;
- 443/tcp:HTTPS;
- 443/udp:需要 HTTP/3 时开放;
- 22/tcp:SSH,但应限制来源、使用密钥并配置独立管理策略。
数据库、Redis、应用内部端口和管理面板不应直接对公网开放。云安全组控制流量能否到达主机,宿主防火墙控制主机和转发路径,Docker 还会创建自己的 NAT 与过滤规则。
这一行为也记录在 Docker 官方的防火墙与端口发布说明(在新标签页打开)中。不要看到 ufw status 为 deny 就认定容器端口不可达。
分别验证容器、宿主机和公网
先看宿主监听和 Docker 映射:
ss -lntup
docker compose ps
docker inspect app \
--format '{{json .NetworkSettings.Ports}}'
再从代理网络验证服务名和容器端口:
docker compose exec proxy getent hosts app
docker compose exec proxy curl -fsS http://app:8080/healthz
再从另一台机器或外部网络检查公网入口:
nc -vz example.com 80
nc -vz example.com 443
nc -vz example.com 8080
预期是 80/443 可达,8080 不可达。端口扫描失败可能来自本地运营商或扫描环境限制,成功则足以证明端口暴露;关键端口还应从至少一个真实外部网络复核。
按故障现象检查监听、绑定和服务名
| 现象 | 原因 | 修正 |
|---|---|---|
proxy 访问 app:8080 被拒绝 | app 只监听容器回环 | app 改为监听 0.0.0.0:8080 |
| 宿主代理无法访问 app | app 没有发布宿主端口 | 使用 127.0.0.1:8080:8080 |
| 数据库能从公网连接 | 5432:5432 发布到全部地址 | 删除 ports,保留 Compose 内部网络 |
| UFW deny 但端口仍可达 | Docker 转发规则先处理流量 | 收紧 publish 地址并配置 Docker 过滤链 |
| 容器间使用 localhost 失败 | localhost 指向当前容器自身 | 改用目标服务名和容器端口 |
| 重建后代理 502 | 代理写死旧容器 IP | 改用 Compose 服务名 |
网络配置变更后执行一次容器重建和主机重启验证。旧容器、残留 NAT 规则或手工端口转发可能让首次测试通过,却在重启后失效。
存储和密钥同样跨越宿主与容器边界,配置方法见Docker 数据卷、文件权限与密钥管理。
常见问题
Docker 端口写成 127.0.0.1:8080:8080 是什么意思?
在 Docker Engine 28.0.0 及以上的默认 NAT 模式中,容器 8080 发布到宿主回环地址的 8080,只有宿主本地进程能通过这个宿主端口访问。旧版本和自定义直连路由需要另行检查。
应用在容器里应该监听 127.0.0.1 还是 0.0.0.0?
需要被其他容器访问时监听 0.0.0.0。公网边界由 ports、宿主防火墙和云安全组控制。
Compose 服务之间通信必须配置 ports 吗?
不需要。同一网络中的服务使用服务名和容器端口通信,ports 只负责发布到宿主机。
启用 UFW 后 Docker 发布端口一定会被拦住吗?
不一定。Docker 的转发规则可能绕过常规 UFW 输入规则,应结合 publish 地址、Docker 过滤链、云安全组和外部探测判断。
怎样确认一个端口没有暴露到公网?
检查进程监听、Docker 端口绑定、宿主 ss、云安全组和外部扫描。任何单层结果都不足以证明完整边界。