假设一台ECS上第一次执行docker compose up -d,应用日志很快出现connection refused;过一会儿手动docker compose restart app,又能正常连接。数据库没有问题,问题出在启动时机:Compose默认只等依赖容器启动,不等数据库可以接受连接。
本文依据Docker Compose和Dockerfile官方文档整理启动顺序的配置与验证方法,未在云实例内运行;服务名、账号和时间参数均为示例,需要按实际环境调整。
1. depends_on短写法只等“已启动”
很多项目里是这样写的:
services:
app:
depends_on:
- db
官方说明是:短写法保证依赖服务先创建、先启动,但不等待依赖服务“健康”。PostgreSQL容器进程起来之后,还要完成初始化或恢复,才开始接受连接;这段时间里应用发起的连接就会失败。
这也解释了“手动重启一次就好了”:第二次启动时数据库早已就绪。问题只在冷启动、主机重启或首次部署时出现,所以容易被当成偶发故障。
2. 给数据库加healthcheck
services:
db:
image: docker.1ms.run/library/postgres:18.6-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${
DB_PASSWORD:?缺少DB_PASSWORD}
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 10s
retries: 5
start_period: 30s
volumes:
pgdata:
几处要点:
- 镜像通过
docker.1ms.run获取,保留官方library命名空间和18.6-alpine固定标签,避免浮动标签让不同机器跑出不同版本。 $${POSTGRES_USER}`中的`$$让Compose不做插值,原样交给容器内的Shell,由容器里的环境变量展开。写成单个$,Compose会在宿主机侧替换,宿主机没有这个变量时就变成空值。- 健康状态最初是
starting,检查通过后变为healthy,连续失败达到retries次后变为unhealthy。 start_period给首次初始化留出时间,期间的失败不计入重试次数;但只要在这段时间里成功一次,后续失败就开始计数。
参数怎么估:大致按“start_period+interval×retries”算出最长等待时间,示例约80秒。首次初始化数据较多或磁盘较慢时,先放大start_period,不要把timeout调得过短:单次检查超过timeout就算失败。
3. 应用侧改成等待service_healthy
services:
migrate:
image: 替换为应用镜像及固定标签
command: ["替换为数据库迁移命令"]
depends_on:
db:
condition: service_healthy
app:
depends_on:
db:
condition: service_healthy
restart: true
migrate:
condition: service_completed_successfully
condition: service_healthy让Compose在db健康后才创建依赖它的服务。项目有数据库迁移时,可以把迁移拆成一次性服务,用service_completed_successfully要求它成功退出后再启动应用;迁移失败时应用不会带着旧表结构启动。没有迁移步骤的项目,去掉migrate相关两段即可。
restart: true表示通过Compose操作更新或重启db时,app也会跟着重启,重新建立连接;它不覆盖容器运行时因崩溃触发的自动重启。该字段在Compose 2.17.0引入,旧版本需要先升级。

4. 验证与边界
docker compose config --quiet && echo '配置解析通过'
docker compose up -d --wait --wait-timeout 180
docker compose ps
db_cid=$(docker compose ps -q db)
docker inspect --format '{
{json .State.Health}}' "$db_cid"
--wait会等服务进入运行或健康状态后再返回,适合放在部署脚本里;等待超时或依赖失败时命令会报错退出,脚本可以据此停下,不必再用固定的sleep猜时间。docker compose ps中db显示为健康后,app才会被创建;State.Health里能看到最近几次检查的输出和退出码,检查失败时先看这里。
这套配置也有边界:
pg_isready只说明数据库在接受连接,不证明业务账号、库表或迁移已经就绪;迁移由上面的一次性服务负责。- 健康检查只控制Compose启动这一刻。数据库在运行中重启时,应用仍需要自己的连接重试。
- 依赖服务被判为不健康时,
up会报依赖启动失败,这时先看数据库日志,不要直接加大重试次数掩盖问题。
记录时我会保留三项:docker compose ps的健康状态、State.Health最近一次输出、应用首次成功连接的时间。三项都有,才能说启动顺序问题已经处理。