SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-07

Docker Compose 网络配置与服务名 DNS 怎么用

了解 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" 将其绑定到回环接口,然后通过 SSH 隧道访问。监听套接字、已发布端口和防火墙规则之间的区别,请参阅Linux 中端口和监听服务的工作方式

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

何时适合使用 network_mode 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 发布端口,以及如何修复

如何通过 4 条命令进行排查

先确认每个容器实际连接到了哪个网络:

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,因为 host 模式容器不连接任何 Docker 网络,也无法解析服务名。

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

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

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

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

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

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

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

因为发布端口由 Docker 添加到 iptables 的转发规则处理。这些规则会先于 UFW 规则匹配,而且转发流量本身也不会经过 UFW 过滤的链。使用 "127.0.0.1:8080:80" 将主机侧绑定到 loopback,并通过 80 和 443 端口上的反向代理发布公网服务。