Docker + Postgres 密码玄学:一次 Nano 自动换行引发的血案与排错实录

在使用 Docker 部署 PostgreSQL 时,明明配置了正确的环境变量却遭遇 FATAL: password authentication failed?本文记录了一次教科书级别的诡异排错过程:从“负负得正”的幽灵密码、持久化卷的初始化盲区,一路追查到 Nano 编辑器复制粘贴时自动换行导致的 Bash 变量断层。踩坑大满贯,值得每一位后端和运维人警惕。

作为一个折腾服务器的独立开发者,经常和 Docker 打交道。本以为 docker-compose up -d 部署个 PostgreSQL 是闭着眼睛都能搞定的常规操作,结果最近却结结实实栽进了一个连环坑里。

报错信息大家都很熟悉:

FATAL: password authentication failed for user "app"

但在我反复核对环境变量、网络配置、甚至是容器间的互通性都没问题后,事情开始变得诡异起来。经过两天的抽丝剥茧,我发现这不仅仅是一个简单的密码拼写错误,而是由幽灵配置、挂载盲区和编辑器特性共同组成的三重陷阱。

这篇博客记录了整个排查过程,希望能帮到同样在终端里怀疑人生的你。


第一重陷阱:“负负得正”的幽灵密码

事情的起因是这样的:我发现新建的应用容器死活连不上 Postgres,但我之前部署的另一个 Auth 服务却能正常连接。

我一开始猜测是不是走 Docker 内部网络(比如 Host=pgsql)就能免密?但 Postgres 的 pg_hba.conf 机制决定了 TCP 连接必须走 scram-sha-256 校验,不可能免密。

通过在容器内部直接 echo 环境变量,我揪出了第一个内鬼——未加引号的 .env 注释

在早期的项目中,我有个.env文件是这样写的:PGSQL_APP_PASSWORD=app@pgsql # 应用通用密码

然后我在docker-compose.yaml中这样写了:

POSTGRES_APP_PASSWORD: ${PGSQL_APP_PASSWORD:-app@pgsql}

因为.env文件没有加双引号,Docker Compose 粗暴地把后面的空格和中文注释一并吃进了环境变量里。

  • 数据库初始化时,把 app@pgsql # 应用通用密码 当成了完整的密码存了进去。
  • 旧的 Auth 容器读取同样的配置,带着这串超长且带中文的密码去请求,两边竟然完美对上号了(负负得正)
  • 而我在命令行手动敲写干净的 app@pgsql,自然被无情拒绝。

避坑指南: .env 文件或 docker-compose.yml 中的复杂字符串,特别是带有特殊字符的密码,务必养成加双引号的习惯


第二重陷阱:被忽视的数据卷(Volume)初始化盲区

发现了上面的问题后,我决定修正配置,改用干净的密码 app#pgsql,并重新启动容器。结果——依然报错!

我进入容器内部打印环境变量,确认 $POSTGRES_APP_PASSWORD 已经是正确的 app#pgsql,为什么还是连不上?

这时候,我注意到了 docker-compose.yml 里的这行代码:

volumes:
  - ./data:/var/lib/postgresql/data

这是 Postgres Docker 镜像的一个经典“潜规则”:只要挂载的目标目录(/var/lib/postgresql/data)不为空,容器启动时就会直接跳过整个初始化流程(包括执行 /docker-entrypoint-initdb.d/ 下的脚本)。

因为我之前已经运行过一次,./data 目录下已经有旧数据了。所以即便我改了配置,重启了容器,数据库里的密码依然是上一次初始化时的那个带中文的“幽灵密码”。

避坑指南: 在开发阶段调试初始化脚本时,如果修改了密码或初始数据库结构,必须清空宿主机的 ./data 目录rm -rf ./data/*),或者进入容器用 ALTER USER 强制修改密码。


最终 Boss:Nano 复制粘贴的“背刺”

好,我删除了旧数据,确信这次一定会重新初始化,docker logs 也清楚地打印出执行了我的自定义脚本 01-init-user-and-permissions.sh,甚至打出了 NOTICE: User created: app

满心欢喜地去测试连接。 报错:password authentication failed

那一刻我真的怀疑自己对 Docker 的认知是不是被篡改了。网络没问题、环境变量没问题、数据也清空重来了,究竟是哪里出了鬼?

直到我打开那个 01-init-user-and-permissions.sh 脚本,一行一行地扫,终于发现了这个极其隐蔽的致命伤:

CREATE USER "$POSTGRES_APP_USER" WITH PASSWORD '$POSTGRES_APP_PASSWO
RD');

是的,你没看错。变量名 $POSTGRES_APP_PASSWORD 被从中间腰斩,换行了!

这是怎么发生的? 因为我是直接在 SSH 终端里用 nano 编辑器,把另一个服务器的脚本复制粘贴进去的。 当你的终端窗口不够宽,且 nano 开启了默认的自动换行(Word Wrap)时,它会在长字符串中间插入一个真正的硬回车(Hard Return)

在 Bash 的逻辑里,它找不到 $POSTGRES_APP_PASSWO 这个变量(因为后面被回车截断了),于是把它解析为空字符串。 最终,数据库真的成功执行了这条 SQL,只不过它执行的是: CREATE USER "app" WITH PASSWORD '';

密码是空的! 这就是我用正确的密码死活连不上的终极原因。


总结与最佳实践

这三个坑叠在一起,节目效果直接拉满。为了防止自己(或者正在看文章的你)再次踩坑,总结以下几条开发铁律:

  1. 终端编辑器防身术: 如果你要在 Linux 终端下用 nano 粘贴长代码或长配置,永远记得带上 -w 参数

    nano -w script.sh
    

    这会禁用自动换行功能,保命必备。更好的做法是使用 VS Code 的 Remote SSH 插件,直接修改服务器文件,告别终端剪贴板折磨。

  2. 环境变量 密码规范: .env密码和包含特殊字符的变量,用双引号包起来。行内注释最好另起一行写。

    # 应用通用密码
    PGSQL_APP_PASSWORD="app@pgsql"
    
  3. 数据库调试三板斧: 当碰到 Docker DB 密码玄学时,直接在宿主机用超管权限查岗,一秒定音:

    # 检查数据库到底吃进去了什么配置
    docker exec -it pgsql /bin/bash
    echo $POSTGRES_APP_PASSWORD
    # 直接用环境变量强制连接测试
    PGPASSWORD="$POSTGRES_APP_PASSWORD" psql -h 127.0.0.1 -U app -d postgres
    

服务器运维就是这样,有时候折磨你两天的并非什么底层内核级 Bug,仅仅是一个看不见的换行符。记录下来,就当是给以后的自己提个醒吧!