为什么默认网络下服务名解析不了
Compose 默认给项目创建一个用户自定义的 bridge 网络,项目内所有服务都接在上面,容器之间可以用服务名互相解析。所以应用连数据库写 `db` 或 `redis` 即可,不需要 IP,也不需要暴露端口到宿主机。
问题出在显式声明 `network_mode: bridge` 或把服务接到 default bridge 网络时。default bridge 没有内嵌 DNS,只能靠 --link 这种已废弃的机制或手写 IP。这两种写法在多机部署时也直接失效,因为容器 IP 每次重建都会变。
因此推荐的做法是:需要互访的服务不要加 ports;只在需要从宿主机或外部访问的服务上声明 ports。需要额外别名时用 networks 下的 aliases,它会在该网络内注册多个名字。
services:
api:
image: acme/api:1.4
networks: [app]
db:
image: postgres:16
networks:
app:
aliases: [database, pg]
volumes: [pgdata:/var/lib/postgresql/data]
networks:
app:
driver: bridge
volumes:
pgdata:expose 与 ports 的区别
expose 只是声明「这个容器会用到这个端口」,供文档和同网络内客户端参考,它不会创建任何到宿主机的映射,也不会让默认网络下的服务获得 DNS 解析能力。它本质上是个元信息。
ports 才会做端口发布:短格式 "8080:80" 表示宿主机 8080 映射到容器 80,长格式可以指定协议和监听地址,如 "127.0.0.1:8080:80/tcp" 只绑定回环地址,外部完全不可达,适合只想本机调试的场景。
注意 -p 与 -P 的区别:-p 显式指定映射,-P 会把镜像 EXPOSE 声明的所有端口随机映射到宿主机高位端口。只用 -P 会让服务意外暴露在所有网卡上,安全上应始终写明 -p。
services:
web:
image: acme/web:2.0
expose:
- "3000"
ports:
- "127.0.0.1:8080:3000"
metrics:
image: prom/node-exporter:v1.8.1
ports:
- "9100:9100"named volume 与 bind mount 的取舍
bind mount 映射宿主机目录,路径由你指定。适合需要宿主机直接编辑配置、挂载已有数据目录、或用 `--mount` 设置 uid/gid 与 ro 权限的场景。缺点是路径依赖宿主机布局,迁移和在 CI 里跑都不方便。
named volume 由 Docker 管理存储位置,跨宿主机和 CI 中表现一致,也更容易设置属主与权限。首次挂载到空目录时,Docker 会把镜像内该路径的原有内容复制进卷,bind mount 则默认遮住镜像内容,需要显式 `nocopy` 才复制。
运维取舍:需要备份和迁移时用 named volume 配合 `docker run --rm -v name:/data -v "$PWD":/backup alpine tar czf /backup/x.tar.gz -C /data .`;需要人工进去改文件、或把配置从仓库挂进去时用 bind mount。两者在同一服务里混用很常见,例如数据用卷、配置用挂载。
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data # named volume:数据
- ./conf/postgresql.conf:/etc/postgresql/postgresql.conf:ro # bind mount:配置
# 备份 named volume
docker run --rm -v pgdata:/data -v "$PWD":/backup alpine tar czf /backup/pgdata.tar.gz -C /data .启动顺序与排障
depends_on 只控制启动顺序和运行期依赖下线顺序,不保证依赖已经可用。数据库容器起来了不代表能接受连接,因此应用反复报 connection refused 是常见现象,而不是配置错误。
需要真正等待就绪时,用长语法加 condition: service_healthy,并在依赖服务上定义 healthcheck。healthcheck 里的检查必须轻量且确定,不要用依赖应用自身的复杂逻辑,否则会形成死锁式的等待。
排查网络问题的顺序是:先 `docker compose ps` 确认容器状态与端口,再进容器用服务名测试连通性(如 `getent hosts db` 或 `curl`),然后用 `docker network inspect` 查看同一网络上的成员与别名。若服务名解析失败,先检查该服务是否真的声明了 networks。
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
api:
depends_on:
db:
condition: service_healthy多 Compose 文件与环境隔离
Compose 支持 -f 叠加多个文件,后面的文件覆盖前面的,这是把基础配置与开发/生产覆盖层分开的最省事做法。基础文件放服务定义与镜像名,覆盖文件只放 ports、environment 和 volumes 的差异。
每个项目默认创建以目录名命名的网络与卷,因此同名项目在同机并行会冲突。用 `docker compose -p <name>` 显式指定项目名,或者在文件里用 name 字段固定,都能保证隔离。生产环境尤其要避免两个环境共享同一个 named volume。
查网络细节时 `docker network inspect` 最直接:关注 Containers 列表里的服务名与别名,以及 IPAMConfig 的子网划分。确认同一个外部服务被多个项目复用时,可以把它声明为 external 网络,由外部统一管理,避免每个项目各建一个网络导致互访失败。
# 基础 + 覆盖
docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose -f compose.yaml -f compose.prod.yaml config # 校验合并结果
# 固定项目名,避免同名目录互相污染
docker compose -p acme-api up -d
networks:
shared:
external: true
name: acme-shared