本节摘要:Compose 项目的服务默认全部接入同一个项目网络,靠服务名互相访问;网络配置要解决的是"谁能访问谁"的问题。本节先讲默认网络的行为与容器 IP 漂移的坑,再讲内置 DNS 的服务名解析原理,随后用双网络示例演示多网络接入与隔离收益,最后覆盖 external 网络复用、ipam 参数与常见排障。网络是服务间通信的骨架,也是安全隔离的第一道闸门。
Docker 本身有一个默认的 bridge 网络,不指定网络时容器都挂在它下面。Compose 更进一步:每个项目启动时自动创建一张以项目名命名的默认网络,项目内所有服务自动加入。所以 2.2 节的例子什么网络配置都没写,nginx、web、db 三个服务照样互通——它们都在项目默认网络里。
默认网络有两个特点要记住。一是用服务名通信:同网络内,服务名就是主机名,DNS 自动解析。二是IP 会漂移:容器重建、网络重建后 IP 可能变化,代码里写死 IP 等于埋雷,重启一次就炸。我们见过太多"昨天还好好的今天连不上"的案例,最后都是写死的 IP 在作祟。结论:应用层永远用服务名,不用 IP。
Compose 创建的网络名是"项目名加下划线加网络名",比如 demo_webnet;同项目内两张网络不会重名,不用刻意记,inspect 时对照即可。
服务名解析靠的是 Docker 内置的 DNS 服务器。容器创建时,Docker 把该容器所属网络里的其他服务名与 IP 的映射注册进 DNS;容器内发起连接时,先向这个内置 DNS 查询,拿到 IP 再建连。整个过程对应用透明,代码里写 db 就像写一个普通主机名。
这个设计把"服务发现"变成了默认能力:web 扩容出三个实例,DNS 里同时挂三个地址,调用方不需要任何改动。容器名也可以当主机名用,但服务名是 Compose 层面的稳定标识,跨环境一致,我们用服务名。
默认网络省事,但"所有服务互通的平面"不适合生产。我们用自定义网络把服务分组:对外入口一组,数据层一组,中间服务按需同时接入两组。
version: "3.9" services: web: image: nginx:1.21 ports: - "80:80" networks: - webnet app: image: my-app:latest networks: - webnet - backend db: image: postgres:14 networks: - backend networks: webnet: driver: bridge backend: driver: bridge
拓扑是:web 只进 webnet,db 只进 backend,app 同时接入两个网络。这样 app 既能被 web 访问,又能访问 db;而 web 与 db 之间没有任何通路。两张网络都声明了 driver: bridge,其实可以省略,Compose 默认就是 bridge;显式写出来是文档化的习惯,也让"这是自定义网络"的意图更明确。
这个配置里有个细节值得注意:app 同时在两个网络里注册了名字。webnet 里的服务解析 app,得到它在 webnet 的地址;backend 里的服务解析 app,得到它在 backend 的地址。同一服务在不同网络下地址不同,但名字一致,这正是多网络接入的便利。
下图从"透视"角度看这个项目:webnet 在前景、backend 在后层,app 服务横跨两个网络,成为唯一的通信桥。

双网络最大的价值是攻击面收敛。web 是唯一暴露到宿主机端口的服务,即使它被攻破,攻击者面对的也只是一个只有 app 的网络,db 不在其中,数据库数据不直接暴露。反过来,db 所在的后端网络里只有可信服务,横向移动的路径被切断。
这个"最小连通"原则可以继续推广:监控探针只进监控网络、备份代理只进备份网络、管理端口永远不映射宿主机。网络配置是免费的防火墙,我们用配置表达"谁该见到谁",而不是事后用 iptables 补洞。
隔离还有一个间接收益:故障域变小。某个网络里的服务异常,不会拖累其它网络的流量;排查时按网络切分日志与监控,问题定位也更快。
默认情况下网络归项目私有,docker-compose down 会把项目创建的网络一并删除。某些场景需要"借用"现成网络:两个项目要共享一个服务,或者项目要接入运维统一建好的网络。这时用 external 声明,Compose 只接入、不创建、不删除:
version: "3.9" services: web: image: nginx:1.21 ports: - "80:80" networks: - my-existing-network networks: my-existing-network: external: true
前提是这个网络已经存在,比如之前用 docker network create my-existing-network 建过。external 网络的生命周期完全由外部管理,down 项目不会动它。跨项目通信由此实现:两个 compose 项目把各自的服务接进同一个 external 网络,服务名互访照样成立。注意同名服务可能冲突,跨项目用别名更稳妥。
external 网络的实际名字和配置里的键不一致时,用 name 字段指认,配置键与真实网络解耦:键名随意,name 指到真实网络名。
| 参数 | 作用 | 示例 |
|---|---|---|
| driver | 网络驱动,默认 bridge | bridge、overlay、host |
| ipam | 自定义网段与网关 | subnet 172.28.0.0/16 |
| enable_ipv6 | 开启 IPv6 | 配合 ipam 配置地址段 |
| external | 复用外部网络 | true 或带 name |
| name | 覆盖生成的网络名 | 便于外部引用 |
需要固定网段时用 ipam:
networks: custom-net: ipam: driver: default config: - subnet: 172.28.0.0/16 gateway: 172.28.0.1
防火墙白名单、VPN 路由、跨环境一致性都会用到固定网段,但日常开发基本不用碰。网段规划要避免与宿主机局域网冲突,172.28 这类私有段是 Docker 的常规选择,多项目各自规划独立网段,别全挤在一个段里。IPv6 同理,enable_ipv6: true 加上地址段配置即可,按需开启。多主机场景用 overlay 网络,把多台 Docker 主机上的容器拉进同一张网,那是 Swarm 的话题,本节点到为止。
容器之间连不上。 先确认两个服务在同一个网络:docker network inspect <网络名> 看成员列表。再确认目标服务真的在运行。防火墙如果挡了容器网段,宿主机层面要放行 docker 网桥。
服务名解析不了。 检查服务是否都接入了自定义网络。默认 bridge 网络没有内置 DNS 的完整支持,容器名解析时好时坏,这也是我们坚持用自定义网络的原因。进容器实测:docker exec -it <容器> sh 后执行 getent hosts <服务名>,能出 IP 就是通的。
通信慢。 先看是不是跨宿主机通信,再看网络驱动是否匹配场景。同机容器间走 bridge 没有瓶颈,慢通常是应用层问题,比如连接串解析了外网地址。
端口映射可以精确到网卡:默认 "8080:80" 对所有网卡开放,只希望本机访问就写 "127.0.0.1:8080:80",外部网络摸不到这个端口,管理面板这类服务建议这么写。只想暴露容器端口、不关心宿主机用哪个端口时,可以只写容器端口 "8080",宿主机随机分配,用 docker-compose port <服务名> 8080 查询实际端口,测试脚本临时起服务很常用。随机端口加 port 查询的组合在 CI 里很好用:每次构建用随机端口避免冲突,测试结束后 down 自动释放。
同一服务还可以挂多个别名:
services: app: networks: webnet: aliases: - api.internal
网络里的其它服务可以用 api.internal 访问 app。别名适合"服务名会变、调用方不想改"的迁移场景,新旧名共存一段时间,平滑过渡。别名还有一个用处:把第三方镜像里写死的地址映射到自己的服务——某个镜像默认连接 example-db 主机名,给它挂个 example-db 别名指向真实数据库服务,镜像一行都不用改。
单机之外还有两条路。overlay 网络把多台 Docker 主机的容器拉进同一张网,是跨主机分布式应用的标准选择,Swarm 模式内置支持,协调网络状态通常需要键值存储配合;macvlan 让容器直接用宿主机的物理网卡,每个容器拿独立 MAC 地址,性能好但受宿主机网络环境限制。overlay 有个硬前提:多台主机必须先组成 Swarm 集群,单独用 docker compose 起不了 overlay,这是集群话题的入口。应用规模再大,服务发现可以交给 Consul、Etcd、ZooKeeper 这类注册中心。这些方向超出单机范围,用到时按需深挖。
网络问题从 docker network ls 开始:列出全部网络,按项目名找到目标;docker network inspect <网络名> 的输出里 Containers 字段列出所有成员,谁没进来一目了然。成员缺失的常见原因不是配置,而是容器没起来——网络成员只在容器运行时注册,容器退出成员就消失,先修容器再查网络。网络排障还有一个盲区:容器内的 /etc/resolv.conf 指向的 DNS 是否正常。自定义网络里 Docker 会注入自己的 DNS 地址,配置被覆盖或网络异常时,检查 resolv.conf 能看出端倪。最后补一条习惯:网络配置改动后一定要重新 up,容器重建才会应用新网络,改完忘了重建,检查半天配置,其实跑的还是旧网络。网络配置宁可少写不可乱写,默认行为比拍脑袋的自定义可靠。
⚠️ 把数据库端口映射到宿主机是网络配置里最常见的安全失误:"5432:5432" 一写,数据库就对整个网络可见。开发调试用完就删,生产环境别写。
💡 服务名解析只在同网络内生效。app 在 webnet 里访问 db 会失败,这不是 bug,是设计——需要跨网络访问的服务,用多网络接入(像示例里的 app 那样同时进两个网络),别把所有服务塞进一张大网。
网络把服务之间的"路"铺好了,接下来要解决的是"参数怎么传"——2.5 节进入环境变量与配置管理。