判断 Docker 端口是否暴露,需要分别查看容器内监听地址、宿主机端口绑定,以及防火墙和云安全组。容器内监听 0.0.0.0,不等于该端口已经暴露到公网。

公网经过云安全组和宿主防火墙访问反向代理,应用与数据库只在 Docker 网络内通信的分层图
应用和数据库只在 Docker 网络通信;公网只能到达反向代理开放的端口。

监听地址、宿主绑定和服务名属于不同网络层

配置位置示例含义
应用进程监听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
宿主代理无法访问 appapp 没有发布宿主端口使用 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、云安全组和外部扫描。任何单层结果都不足以证明完整边界。