Compose 启动时出现 Waiting for network,第一反应往往是调大超时时间,但这样可能把真正的问题掩盖掉。这个提示可能出现在容器启动阶段,也可能出现在应用初始化阶段,两者处理方式完全不同。下面按排查顺序整理,每一步都尽量说清楚动作、验证方式和风险边界。
先别急着改配置
当 Compose 启动卡在 Waiting for network 时,先别急着改配置。执行 docker compose up -d 后,用 docker compose logs -f 观察各容器输出,同时开另一个终端运行 docker events --filter event=start --filter event=health_status。看是容器一直没进入运行状态,还是已在运行但应用初始化时网络探测失败。如果是后者,问题多半出在容器内的启动脚本或应用配置,和 Docker 本身的网络创建没关系。常见坑是只看容器状态是 Up 就以为网络可用,实际应用的网络连接尚未建立。也可以临时进入容器执行 curl 或 ping 目标地址,确认网络路径是否提前通了。
这一步的关键是区分“容器没起来”和“应用没就绪”。如果 events 显示容器已经 start,但 health_status 一直不更新,说明应用还在做网络探测。此时重点看应用日志,而不是反复 docker compose restart。有些镜像会先执行初始化脚本再启动进程,脚本里的网络请求慢,也会表现为 Waiting for network。
不要再用 sleep 硬等依赖
很多 Compose 文件里会写 command: /bin/sh -c “sleep 10 && exec app”,这种硬编码等待很脆弱。更好的做法是为依赖方配置 healthcheck,例如检测端口或 HTTP 接口。先给数据库或消息队列加上 healthcheck,再把业务容器的 depends_on 改成 condition: service_healthy。注意 healthcheck 的 test 命令要避免选用容器内没有的工具,比如没有 curl 就先装好或用内置命令。一个常见坑是 healthcheck 写错路径导致始终 healthy: false,结果整体启动时间反而变长。如果基础镜像精简,可以用 wget 或 /dev/tcp 探测。
healthcheck 的编写需要针对具体服务。比如 PostgreSQL 可以用 pg_isready,Redis 可以用 redis-cli ping,HTTP 接口可以用 wget -qO- 或 curl。确定 test 命令是否可用,可以先用 docker exec <service> which curl 检查。更重要的一点:healthcheck 的间隔和超时要设置合理,否则启动阶段会频繁探测,加重负载。可以将 start_period 设为 10s,让容器有初始化时间,再开始记录失败。
depends_on 条件不是写法越新越好
旧版 Compose 的 depends_on 只保证依赖容器先启动,不会等它服务就绪,所以经常出现业务连不上数据库而崩溃。Compose 规范里可以用 condition: service_healthy,但要注意不是所有版本都支持,文件必须声明 version 3.4 以上,且 swarm 模式下会忽略该字段。操作时把既有的普通 depends_on 升级为带条件的写法;如果服务不写 healthcheck,这个条件永远不通过,启动会一直等待。另一个边界是循环依赖不能用这种条件解决,需要拆解结构或让某服务延迟注册。常见坑是同时设置 restart: always 和 condition,容器会反复重启,重启日志刷屏但问题定位不了。
如果使用的是新版 Docker Compose V2,version 字段其实已经废弃,但这里的判断依然适用:必须有 healthcheck。建议先为每个被依赖服务写健康检查,再改动 depends_on。验证方式是用 docker compose config 检查渲染后的配置,确认 conditions 字段存在。如果发现循环依赖,需要把其中一个服务拆出去,例如通过消息队列解耦。
默认网络和服务名解析也可能拖慢启动
容器之间通过服务名互访时,建议使用自定义 bridge 网络,而不是默认的 default bridge。默认网络虽然也做 DNS 解析,但容器重建后别名更新较慢,可能造成短时间 Unknown host 或连接拒绝。在 docker-compose.yml 里显式写一个 networks 段,把服务都拉进同一网络,内部请求会顺畅很多。注意不要使用 network_mode: host,它会完全绕过 Compose 的 DNS 解析,让服务名变成外部地址,反而更容易触发网络等待。如果必须用 host,就把内部地址改为 localhost 或固化的真实 IP。
services:
app:
networks:
- app-net
db:
networks:
- app-net
networks:
app-net:
driver: bridge
启动后用 docker compose exec app getent hosts db 查看解析是否正常。如果解析不到,检查网络名和容器是否在同一个网络。
先排除镜像拉取这个隐形原因
Waiting for network 不一定来自应用启动,也可能是 docker compose up 在拉取镜像。观察终端输出,如果长时间停在 Pulling 或 snapshot 阶段,就说明镜像下载是瓶颈。判断之后可以这样做:本地先执行 docker pull 把镜像提前拉好,避免在 compose up 时拉取;给 Docker 配置 registry-mirror 或使用内网镜像仓库;在 Dockerfile 里把不常变动的基础镜像和依赖层放在前面,提高缓存命中率。另外,尽量避免对镜像使用 latest 标签,固定到具体版本能减少不必要的远程检查。
这套做法的收益取决于当前网络和镜像源,建议先看 docker pull 的实际耗时,再决定要不要换镜像源。配置 registry-mirror 后需要重启 Docker 守护进程,它只影响新镜像的拉取,不改变已有镜像的启动行为。
应用重试和超时参数也是可调项
如果前面都正常,但启动就是慢,看应用日志里两条重试日志的时间间隔。很多框架默认用指数退避,初始等待几秒,后面会越来越长。比如 HTTP 客户端连接超时、数据库连接池初始化重试,都可能成为等待源头。可以在环境变量里覆盖默认配置,例如 DB_CONNECT_TIMEOUT=5 或 RETRY_INTERVAL=2,具体要看程序是否真的读取这些变量。在 compose 文件里加环境变量前,先查一下应用文档或源码里的配置名。
调小超时意味着应用在网络抖动时更容易放弃连接,所以不要同时把最大重试次数也降得太低。配合 restart: unless-stopped 或 on-failure,让容器在失败后自动拉起,这样整体启动速度会更快,但要注意不要和 depends_on condition 冲突。
处理 Waiting for network 的整个过程,可以按“先定位、再改依赖、修网络、查镜像、调参数”的顺序走。每次只改一个地方,然后用 docker compose up -d 观察启动日志,配合 docker container inspect 看健康状态。如果改动后启动更慢或失败,先回滚刚才的配置。生产中宁可保留适度的重试,也不要盲目追求秒级启动,因为容器内服务的就绪时间可能受宿主机负载影响。