适用:使用 systemd 的 Linux 服务器、常规 rootful Docker Engine;Compose 示例使用
docker composeV2。本文为教学故障场景,错误文本和结果为示意,具体输出以现场为准。
一、遭遇情况:第一次部署失败,手动重启应用又好了
执行 docker compose up -d 后,数据库容器正在运行,应用却因 connection refused 退出。等半分钟再启动应用,就能连上。于是团队在启动脚本里加了 sleep 30。
本篇案例是数据库首次初始化比应用启动慢。固定等待时间在测试机上可能奏效,换一台慢磁盘服务器或增加初始化数据后,仍然可能失败。
二、原因分析:至少存在三种不同状态
容器主进程已启动;数据库已开始接受连接;应用使用指定账号、数据库和表可以完成请求。这三个阶段并不等价。
depends_on 的简单形式主要表达启动依赖,不能自动代表数据库已经达到应用要求的就绪状态。应通过明确的健康检查和依赖条件,让初次启动等待达到对应阶段。

三、排查思路:先证明是时间问题,不要先增加等待
在正确项目目录执行:
docker compose version
docker compose ps -a
docker compose logs --tail 100 --timestamps db
docker compose logs --tail 100 --timestamps app
docker compose config
实际服务不叫 app 时替换名称。核对应用失败时间是否早于数据库接受连接时间,并检查应用地址使用数据库服务名与容器内端口。
若日志是密码认证失败、数据库不存在或表不存在,不是单纯端口未就绪。等待更久不会自动修正错误密码;健康检查也不能代替数据迁移。
docker compose config 可能展开敏感环境变量,完整输出仅在受控终端查看,分享时先脱敏。它用于确认最终配置,不是可以随意公开的排查附件。
四、实践:一个完整、可独立运行的 Compose 项目
创建新的练习目录,不要覆盖生产 Compose 文件。本实验使用 PostgreSQL 16 的 Alpine 镜像,数据库不发布宿主机端口。check 是一次性连接检查任务,成功后退出 0 是预期行为。
在目录里建立 .env,填入你自己的练习密码:
LAB_DB_PASSWORD=ReplaceWithYourOwnLabPassword
不要提交真实 .env 到代码仓库。下面是完整的 compose.yaml:
name: dt14-readiness
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: labuser
POSTGRES_DB: labdb
POSTGRES_PASSWORD: ${LAB_DB_PASSWORD:?set LAB_DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""]
interval: 3s
timeout: 3s
retries: 20
start_period: 10s
check:
image: postgres:16-alpine
environment:
PGPASSWORD: ${LAB_DB_PASSWORD:?set LAB_DB_PASSWORD}
depends_on:
db:
condition: service_healthy
entrypoint: ["/bin/sh", "-ec"]
command:
- "psql -h db -U labuser -d labdb -v ON_ERROR_STOP=1 -c 'SELECT 1;'"
volumes:
pgdata: {}
$$ 让 Compose 保留美元符号,交给容器内 shell 展开数据库环境变量。这里使用 TCP 回环地址检查数据库,避免把初始化时某些仅 Unix socket 可用阶段误当作外部连接已可用。
检查语法并启动:
docker compose config --quiet
docker compose up -d
docker compose ps -a
docker compose logs --tail 50 db check
预期 db 到达 healthy,check 输出查询结果 1 后退出 0。查看具体状态:
docker inspect "$(docker compose ps -q db)" --format '{{.State.Health.Status}}'
docker inspect "$(docker compose ps -a -q check)" --format 'Exit={{.State.ExitCode}}'
第二条使用 ps -a,因为检查任务已经结束。只看到它不在运行列表里,并不代表失败。
五、健康检查能保证什么,不能保证什么
pg_isready 主要判断 PostgreSQL 是否在接受连接,不是完整验证业务账号、数据库结构和查询权限。所以本实验另有 check 真正执行 SELECT 1;真实应用还需要根据业务验证迁移和必要对象。
start_period 为启动阶段失败计数提供宽限,不是强制固定睡眠。检查成功后,服务可以进入健康状态;参数需要结合初始化时间与失败判断要求设置。
如果把本例改为简单 depends_on: [db],首次初始化时 check 可能抢跑失败,但这个竞争条件不保证每次都复现。偶尔成功,不能证明部署定义没有时序问题。
六、持续运行还需要应用重连
service_healthy 解决 Compose 初次启动时的依赖等待,不会让数据库后来重启时,应用现有连接自动恢复。应用仍需有连接超时、有限重试、退避和连接池失效处理。
正常维护数据库时,必须按业务考虑连接恢复;不要把无限重启应用或固定 sleep 当成持续可用方案。
本实验结束可在专用目录执行 docker compose down 停止并删除项目容器和网络,默认保留命名卷。不要附加 -v 来解决启动故障,卷里可能有需要保留的数据。
七、验证与总结
确认依赖健康、应用真实连接成功,再观察数据库重启后的恢复能力。生产验收要按真实业务口径,而不只是 SELECT 1。
这类问题的关键是明确就绪条件,让初次等待和运行期重连各司其职。容器启动顺序可以控制,数据库初始化速度却不能靠一个固定秒数可靠推断。