写了 depends_on 仍连不上 PostgreSQL:检查地址、就绪与初始化
区分容器启动、TCP 就绪和初始化完成,解释服务名、健康检查以及官方 PostgreSQL 镜像临时 socket 进程对连接失败的影响。
Compose 里已经写了 depends_on,应用启动时仍报 PostgreSQL Connection refused,手动重启应用又能连接。先区分三个问题:应用连接的是不是数据库容器、数据库有没有开始接受 TCP 连接、初始化脚本有没有完成。depends_on 的短写法只安排启动顺序,不保证这三件事都完成。
“数据库已经启动”可能只指临时进程
初始化脚本连接 localhost:5432 失败时,即使前一条日志写着数据库已准备好,也要核对当时启动的是哪个进程。数据库容器环境中的 PGHOST=localhost 会让客户端尝试 TCP 连接。
官方镜像初始化期间启动的是只监听 Unix socket 的临时数据库进程;正式对外监听 TCP 的进程随后才启动。/docker-entrypoint-initdb.d/ 中的脚本不应强行通过 TCP 连接这个临时进程。
这与应用容器过早连接数据库是不同分支。给应用加等待,不能修复初始化脚本自己使用了错误连接方式;给初始化脚本改 socket,也不能替代应用的就绪等待。
先按报错地址判断连接去了哪里
如果应用容器报错地址是 localhost、127.0.0.1 或 ::1,而 PostgreSQL 在另一个容器中,先改数据库主机名。普通 Compose 网络中,应用通过服务名和容器内端口连接,比如 db:5432。localhost 指应用容器自身;宿主机的发布端口是给宿主机或外部访问使用的。
如果地址已经是 db:5432,再看数据库日志的时间顺序:失败发生在初始化、恢复期间,还是正式服务已经持续运行之后。读状态可以用:
docker compose ps -a
docker compose logs --tail=100 db
docker compose logs --tail=100 app
这里假设服务名是 db 和 app,实际使用前替换。日志可能含连接串,不要把密码一并贴到问答社区。持续拒绝连接应继续检查监听地址和端口;只有刚启动时失败、稍后恢复,才更符合启动时序问题。若报错变成密码认证失败或表不存在,应进入认证或数据库迁移分支,不能继续只增加等待秒数。
用健康检查等待正式 TCP 服务
下面是适用于 Docker Compose v2 的配置片段。示例使用 PostgreSQL 17;它不是给现有数据库做跨版本升级的配置:
services:
db:
image: postgres:17
environment:
POSTGRES_USER: app
POSTGRES_DB: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 5s
timeout: 3s
retries: 12
start_period: 20s
app:
# 保留你原来的 image/build、连接配置及其他属性
depends_on:
db:
condition: service_healthy
app 仍需完整的镜像或构建配置以及自己的连接串,这段只展示依赖设置。$$ 让变量留给容器中的 shell 展开,避免 Compose 提前替换。显式检查 TCP 地址,可以避开初始化临时进程仅有 socket 就被判定就绪的情况。pg_isready 判断服务器是否接受连接,不能证明应用密码正确、目标表存在或迁移已经成功。
若必须等待独立迁移任务完成,可以用 service_completed_successfully 表达一次性任务成功退出的依赖。但要确认迁移任务确实返回正确退出码,不能把所有长驻服务都改成这个条件。
应用还应有有限次数的重试或退避,处理数据库运行期间短暂中断。Compose 启动时通过一次健康检查,并不保证数据库以后一直可用。
初始化脚本失败时,检查 PGHOST
如果失败发生在数据库容器自己的 /docker-entrypoint-initdb.d/ 脚本中,检查有没有继承 PGHOST=localhost 或显式写 psql -h localhost。按官方镜像约定,在初始化脚本里通过本地 socket 连接,例如下面的只读 SQL 片段:
# 仅用于官方镜像的初始化脚本环境
unset PGHOST
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" \
--dbname "$POSTGRES_DB" -c 'SELECT 1;'
初始化脚本只在数据目录为空时执行。失败后重新启动容器,不保证失败的建表或建用户语句会自动补跑;已有数据目录可能直接跳过初始化。先核对哪些对象已创建,再用受控迁移修复缺失部分。不要为了让脚本重跑而删除保存业务数据的数据卷。
修复后要分别确认三件事
数据库应在完成初始化后接受 TCP 连接,应用日志应能用自己的账号正常连接,业务请求应能访问需要的表。若要检查启动顺序,选择维护窗口重启相关服务;先保存配置和必要的数据备份,失败时恢复原配置,不切换 PostgreSQL 主版本。
只看到 healthy 还不够;只看到重启应用后恢复,也还不能证明初始化脚本已经执行完整。把服务器就绪、账号认证和业务对象准备分别确认,才能找到这个场景中真正缺少的一步。


