Postgres Docker镜像偶发启动失败问题排查求助
Postgres容器偶尔启动失败:init.sql环境变量未替换的原因与解决方法
问题背景与配置
我正在开发一个用Docker Compose部署Postgres服务的Python项目,相关配置如下:
compose/postgresql/Dockerfile
FROM postgres:16.1-bullseye COPY init.sql /docker-entrypoint-initdb.d/init.sql
compose/postgresql/init.sql(基础账号配置)
-- Create database & user CREATE DATABASE ${POSTGRES_DB}; CREATE USER ${POSTGRES_USER} WITH ENCRYPTED PASSWORD ${POSTGRES_PASSWORD}; -- Grant privileges GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_USER} WITH GRANT OPTION; GRANT USAGE ON SCHEMA public TO ${POSTGRES_USER}; GRANT SELECT ON ALL TABLES IN SCHEMA public TO ${POSTGRES_USER}; -- Alter default privileges ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO ${POSTGRES_USER}; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO ${POSTGRES_USER};
docker-compose.yaml(Docker Compose V2)
services: postgres: build: context: compose/postgresql ports: - "5432:5432" environment: - POSTGRES_USER=app-user - POSTGRES_PASSWORD=S3cr3tP4ssw0rd - POSTGRES_DB=app-db python-service: [...]
问题现象
本地测试时,Python服务会在Postgres启动10秒后启动(Postgres本身启动仅需2-3秒),但偶尔(约每10次运行出现1次)Postgres容器启动失败,报错无法解析init.sql:
postgres-1 | /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/init.sql postgres-1 | psql:/docker-entrypoint-initdb.d/init.sql:2: ERROR: syntax error at or near "$" postgres-1 | LINE 1: CREATE DATABASE ${POSTGRES_DB}; postgres-1 | ^ postgres-1 | 2024-05-07 08:13:10.214 UTC [61] ERROR: syntax error at or near "$" at character 17 postgres-1 | 2024-05-07 08:13:10.214 UTC [61] STATEMENT: CREATE DATABASE ${POSTGRES_DB}; postgres-1 exited with code 3
原因分析
这个“随机失败”并非真正随机,是由Postgres镜像的初始化逻辑决定的:
- Postgres官方镜像的
docker-entrypoint.sh仅在**数据库首次初始化(对应的数据卷为空)**时,才会执行/docker-entrypoint-initdb.d/目录下的初始化文件。 - 对于
.sql格式的文件,entrypoint会直接调用psql执行,但psql无法识别shell风格的${VAR}变量,因此会抛出语法错误。 - 当数据卷已存在(之前成功初始化过数据库),entrypoint会跳过初始化步骤,容器正常启动;只有当数据卷被清空(比如首次启动、手动删除数据卷、
docker-compose down -v清理卷),才会触发init.sql的执行,进而报错。
解决方法
有三种可行方案,推荐前两种以保持配置灵活性:
方案一:用shell脚本+模板文件实现变量替换
将初始化逻辑改为可执行的shell脚本,利用envsubst替换模板中的环境变量后执行:
- 重命名原
init.sql为init.sql.tmpl(内容保持不变) - 创建
init.sh脚本:
#!/bin/sh set -e # 替换模板中的环境变量,然后传给psql执行 envsubst < /docker-entrypoint-initdb.d/init.sql.tmpl | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "postgres"
- 修改Dockerfile,复制两个文件并给脚本加执行权限:
FROM postgres:16.1-bullseye COPY init.sh /docker-entrypoint-initdb.d/init.sh COPY init.sql.tmpl /docker-entrypoint-initdb.d/init.sql.tmpl RUN chmod +x /docker-entrypoint-initdb.d/init.sh
方案二:直接在shell脚本中写SQL(无需模板)
直接在shell脚本中使用环境变量编写SQL逻辑:
- 创建
init.sh脚本:
#!/bin/sh set -e # 直接通过shell变量注入SQL逻辑 psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "postgres" <<-EOSQL CREATE DATABASE $POSTGRES_DB; CREATE USER $POSTGRES_USER WITH ENCRYPTED PASSWORD '$POSTGRES_PASSWORD'; GRANT ALL PRIVILEGES ON DATABASE $POSTGRES_DB TO $POSTGRES_USER WITH GRANT OPTION; GRANT USAGE ON SCHEMA public TO $POSTGRES_USER; GRANT SELECT ON ALL TABLES IN SCHEMA public TO $POSTGRES_USER; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $POSTGRES_USER; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $POSTGRES_USER; EOSQL
- 修改Dockerfile:
FROM postgres:16.1-bullseye COPY init.sh /docker-entrypoint-initdb.d/init.sh RUN chmod +x /docker-entrypoint-initdb.d/init.sh
方案三:利用镜像自带的自动初始化简化配置
Postgres官方镜像已经支持通过POSTGRES_DB、POSTGRES_USER、POSTGRES_PASSWORD自动创建对应的数据库和用户,无需手动编写这部分SQL:
- 简化
init.sql,仅保留权限授予逻辑(注意替换为实际的用户名和库名):
-- Grant privileges GRANT ALL PRIVILEGES ON DATABASE app-db TO app-user WITH GRANT OPTION; GRANT USAGE ON SCHEMA public TO app-user; GRANT SELECT ON ALL TABLES IN SCHEMA public TO app-user; -- Alter default privileges ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO app-user; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO app-user;
- Dockerfile保持原配置即可。
注:此方案灵活性较低,若后续修改环境变量需同步修改init.sql。
内容的提问来源于stack exchange,提问作者Adrien
相关产品推荐
相关产品推荐

