SSD Nodes Learn 8GB 内存 — 每年 $66
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-01

Docker Compose 网络:服务名 DNS、host 模式与端口发布

了解 Compose 默认桥接网络、按服务名解析 DNS、host 模式的适用场景、跨项目共享网络,以及绕过 UFW 的已发布端口。

应用启动前 Compose 构建的内容

Docker Compose 网络从一条规则开始:docker compose up 会为项目创建一个私有网络,将每个服务连接到该网络,并允许这些服务通过服务名称相互访问。要实现这一点,您不需要编写任何 networks: 行。许多 Compose 网络问题都源于不了解默认网络已经存在。

下面是一个小型文件。将其保存为目录 shop 中的 compose.yaml

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example

启动项目,然后查看 Docker 创建的内容:

docker compose up -d
docker network ls

列表中现在有一个名为 shop_default 的网络。Compose 将其命名为 <project>_default,项目名称默认采用目录名称的小写形式。您可以使用 docker compose -p myproject up -d 覆盖项目名称,也可以在文件中使用顶层 name: myproject。该网络的驱动程序是 bridge,它相当于主机内部的虚拟交换机。每个容器都会在私有子网上获得一个地址,出站流量离开时会转换为主机的地址。

docker compose down 会再次删除该网络。因此,旧项目中的过时容器可能会使网络保持打开状态:Docker 会返回 error while removing network: network shop_default has active endpoints,解决方法是停止或删除仍连接到该网络的容器。

如果您刚开始使用 Compose,建议先阅读Compose 文件布局和生命周期命令,因为下面的内容都假设您已经能够启动和停止项目。

初学者容易忽略的是按服务名称进行 DNS 解析

在任何用户定义的网络上,Docker 都会运行一个内置 DNS 服务器,每个容器都可以通过 127.0.0.11 访问它。该服务器会将服务名称解析为当前容器的地址。因此,web 无需任何配置,即可通过主机名 db 在端口 5432 访问数据库。

docker compose exec web getent hosts db

该命令会输出类似 172.18.0.2 db 的行。如果没有任何输出,说明这两个服务不在同一网络上。

几乎所有人都会犯一次的错误,是在应用配置中使用 localhost。在容器内,localhost 指向当前容器,而不是主机,也不是其他服务。Postgres 客户端会明确报告这一点:

could not connect to server: Connection refused
	Is the server running on host "localhost" (127.0.0.1) and accepting
	TCP connections on port 5432?

连接字符串应为 postgresql://postgres:example@db:5432/postgres。其中的主机部分是服务名称。

还有两点可以避免后续浪费时间。名称解析的结果取决于当前运行的容器,因此 docker compose up -d --scale web=3 会为一个名称返回三个地址;如果客户端永久缓存 DNS 记录,就会一直指向已失效的容器。此外,未使用 --network 的普通 docker run 使用的是旧式 bridge 网络,该网络完全不提供名称解析。这就是为什么有关 2016 年容器链接的建议与你当前看到的情况不一致。

您不需要 ports: 来连接两个服务

ports: 会将容器端口发布到主机。它用于接收来自 Docker 外部的网络流量。它与服务之间的流量无关,因为项目网络上的服务之间已经可以使用整个端口范围进行通信。

因此,许多人添加到数据库服务中的 ports: - "5432:5432" 不仅没有作用,还会造成实际危害:它会在服务器的公共接口上暴露 Postgres。请删除它。如果您需要从笔记本电脑访问该服务以执行迁移,请使用 "127.0.0.1:5432:5432" 将其绑定到 loopback,然后通过 SSH 隧道访问。监听套接字、已发布端口和防火墙规则之间的区别,请参阅Linux 上的端口和监听服务工作原理

在 Compose 中,expose: 仅用于文档说明。它不会打开任何端口,因为同一网络中的容器之间没有端口被关闭。

host 网络模式适用的场景及其代价

host 模式会移除容器自身的网络命名空间,并允许进程直接使用主机的网络接口。

services:
  probe:
    image: alpine:3.20
    network_mode: host
    command: sleep infinity

使用 host 模式有实际需求。需要查看本地网络中广播或组播流量的进程,例如媒体服务器或家庭自动化中枢的设备发现功能,无法从 bridge 后面查看这些流量,因为 bridge 不会将其转发到容器。读取主机网络接口计数器的监控代理也需要访问主机的网络接口。此外,它会跳过地址转换环节,这在数据包速率较高时很重要。

这些代价是明确的。

ports: 将无法使用。Docker 会警告,使用 host 网络模式时会丢弃发布的端口,容器会绑定其进程实际绑定的端口。两个 host 模式容器都要使用端口 8080 时会发生冲突,第二个容器会因 bind: address already in use 退出。

双向的服务名解析都会失效。容器不在项目网络中,因此无法解析 db,其他服务也无法解析它。容器只能通过主机上发布的端口访问这些服务,通常使用 127.0.0.1

隔离性会消失。在 host 模式容器内绑定 0.0.0.0 的进程会监听服务器的所有网络接口,包括公网接口,其行为与使用 apt 安装的软件包完全相同。这样也有一个优点:此流量会经过正常的入站路径,因此 UFW 规则会对其生效,而发布的端口并非如此。

host 模式是 Linux Docker Engine 的功能。Docker Desktop 仅从版本 4.34 开始支持该模式,并且必须先启用它。该模式还有其他限制:容器无法绑定主机 IP 地址,并且只处理 TCP 和 UDP。如果团队一半使用 Linux 服务器,另一半使用 Docker Desktop,同一个文件可能会产生不同的行为。

只有在需要使用主机网络接口时才使用 host 模式。不要用它来解决连接问题,因为它通常会将一个问题替换成更难处理的问题。

使用外部网络连接两个 Compose 项目

一个项目创建的网络对另一个项目不可见。因此,proxy/compose.yaml 中的反向代理无法访问 app/compose.yaml 中的应用,即使它们运行在同一台服务器上。解决方法是使用一个不属于任何项目的网络。

手动创建一次:

docker network create edge

然后在两个项目中都将其声明为外部网络。代理端:

services:
  proxy:
    image: traefik:v3.1
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - edge

networks:
  edge:
    name: edge
    external: true

应用端:

services:
  app:
    image: nginx:1.27
    networks:
      - edge
      - internal
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: example
    networks:
      - internal

networks:
  edge:
    name: edge
    external: true
  internal:

external: true 告诉 Compose 挂载现有网络,而不是创建网络,并在 docker compose down 上保留该网络。单独的 name: 键比看起来更重要:没有它,Compose 会查找名称必须完全为 edge 的网络;有了它,您可以在配置文件中使用一个名称,在主机上使用另一个名称。

如果网络不存在,Compose 会拒绝启动,并报告该网络已声明为外部网络但找不到。请先创建网络。

注意应用配置文件如何处理 internal。数据库只连接到该项目的本地网络,因此代理无法访问它,只有 app 可以访问。将 internal: true 添加到网络下方会进一步完全删除数据库容器到外部网络的路由。对于数据库,这是一个很好的默认设置,但在启用前需要了解一个代价:连接到内部网络的容器无法下载任何内容,因此启动时运行 apt-get updatepip install 的entrypoint会挂起,随后因超时而失败。

如需查看包含路由规则和证书的完整配置示例,请参阅在一个 Traefik 实例后运行多个应用

发布的端口会绕过 UFW

这是 Compose 网络配置可能演变为安全事件的部分。您发布一个端口,确认 UFW 已启用并拒绝除 SSH 之外的所有连接,但该服务仍可从互联网访问。

sudo ufw status
curl http://203.0.113.10:8080

UFW 显示该端口已被阻止。curl 仍然返回页面。一切运行正常。Docker 会直接将自己的地址转换和转发规则写入 iptables。发往已发布容器端口的流量会被转发到容器,而不是交付给主机,因此不会经过 UFW 管理的本地目标流量链。Docker 的规则也会先于 UFW 匹配。

简便的修复方法是只在需要的位置发布端口:

    ports:
      - "127.0.0.1:8080:80"

这样会将主机端绑定到 loopback。该端口可以从服务器本身和 SSH 隧道访问,其他位置则无法访问。将公共入口放在反向代理后面,并有意发布 80 和 443。完整说明(包括必须筛选已发布端口时使用的 DOCKER-USER 链)请参阅为什么 Docker 会直接绕过 UFW 发布端口以及如何修复

四条命令排查问题

首先确认每个容器实际连接的是哪个网络:

docker network inspect shop_default

Containers 块会列出所有已连接容器及其地址。未出现在列表中的服务可能连接到了其他网络、处于 host 模式,或尚未运行。

从连接到同一网络的一次性容器中测试名称解析。这样无需在自有镜像中安装任何工具:

docker run --rm --network shop_default busybox nslookup db
docker run --rm --network shop_default busybox nc -zv db 5432

nslookup 失败表示名称解析或网络成员关系存在问题。nslookup 成功而 nc 失败,表示服务正在运行,但未监听该端口,或者在自己的容器内监听的是 127.0.0.1 而不是 0.0.0.0。开发服务器经常出现后一种情况。应修改应用的绑定地址,而不是修改 Docker 配置。

还有一种看起来像 Docker 错误的故障。如果容器之间可以通信,但无法访问办公网络或 VPN 网络中的计算机,则 Docker 子网可能与该网络重叠。Docker 默认从 172.17.0.0/16 开始分配地址池。请在 /etc/docker/daemon.json 中修改地址池:

{
  "default-address-pools": [
    { "base": "10.200.0.0/16", "size": 24 }
  ]
}

然后运行 sudo systemctl restart docker 并重新创建受影响的网络,因为现有网络会保留创建时使用的子网。

FAQ

为什么我的容器无法通过服务名相互访问?

它们不在同一个网络上。Compose 会自动将每个服务连接到 <project>_default,但只要为某个服务添加 networks: 列表,该列表就会成为该服务的完整网络集合,不再隐含默认网络。运行 docker network inspect <network>,检查两个容器是否都出现在 Containers 块中。还要检查两个服务是否都没有使用 network_mode: host,因为使用主机模式的容器不连接任何 Docker 网络,无法解析服务名。

一个服务访问另一个服务时,是否需要发布端口?

不需要。在 Compose 网络中,每个容器的所有端口都可由该网络上的其他容器访问。ports: 仅用于允许 Docker 外部的流量访问容器,expose: 仅用于提供文档说明。发布数据库端口是一种常见且有风险的做法,因为这会将数据库暴露在服务器的公共接口上。

bridge 网络和 host 网络有什么区别?

bridge 会为容器提供独立的网络命名空间,并在虚拟交换机上分配地址,同时支持容器间的自动名称解析和出站流量转换。host 会直接使用主机的网络协议栈:没有独立地址,无法通过服务名解析,不需要发布端口,也不与主机上的其他监听程序隔离。bridge 是默认选项,除非进程需要使用主机的网络接口,否则应使用 bridge。

如何连接来自两个不同 Compose 文件的容器?

使用 docker network create edge 创建共享网络,然后在两个文件中通过 external: true 声明该网络,并将需要通信的服务连接到该网络。Compose 不会创建或删除该网络。如果跳过创建步骤,Compose 会拒绝启动,并报告网络已声明为外部网络但未找到。

为什么 UFW 阻止端口后,我的容器仍可从互联网访问?

因为发布端口由 Docker 添加到 iptables 的转发规则处理。这些规则会在 UFW 规则之前匹配,而且转发流量本身不会经过 UFW 过滤的链。使用 "127.0.0.1:8080:80" 将主机端绑定到 loopback,并将所有公共服务放在 reverse proxy 后面,通过端口 80 和 443 提供访问。