<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Linux on 友派博客</title><link>https://blog.uipad.com/zh-cn/tags/linux/</link><description>Recent content in Linux on 友派博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sun, 26 Apr 2026 21:30:00 +0800</lastBuildDate><atom:link href="https://blog.uipad.com/zh-cn/tags/linux/index.xml" rel="self" type="application/rss+xml"/><item><title>Docker + Postgres 密码玄学：一次 Nano 自动换行引发的血案与排错实录</title><link>https://blog.uipad.com/zh-cn/post/2026-04/docker-postgres-auth-nano-wrap-bug/</link><pubDate>Sun, 26 Apr 2026 21:30:00 +0800</pubDate><guid>https://blog.uipad.com/zh-cn/post/2026-04/docker-postgres-auth-nano-wrap-bug/</guid><description>&lt;p&gt;作为一个折腾服务器的独立开发者，经常和 Docker 打交道。本以为 &lt;code&gt;docker-compose up -d&lt;/code&gt; 部署个 PostgreSQL 是闭着眼睛都能搞定的常规操作，结果最近却结结实实栽进了一个连环坑里。&lt;/p&gt;
&lt;p&gt;报错信息大家都很熟悉：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;FATAL: password authentication failed for user &amp;quot;app&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但在我反复核对环境变量、网络配置、甚至是容器间的互通性都没问题后，事情开始变得诡异起来。经过两天的抽丝剥茧，我发现这不仅仅是一个简单的密码拼写错误，而是由&lt;strong&gt;幽灵配置、挂载盲区和编辑器特性&lt;/strong&gt;共同组成的三重陷阱。&lt;/p&gt;
&lt;p&gt;这篇博客记录了整个排查过程，希望能帮到同样在终端里怀疑人生的你。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="第一重陷阱负负得正的幽灵密码"&gt;第一重陷阱：“负负得正”的幽灵密码
&lt;/h3&gt;&lt;p&gt;事情的起因是这样的：我发现新建的应用容器死活连不上 Postgres，但我之前部署的另一个 Auth 服务却能正常连接。&lt;/p&gt;
&lt;p&gt;我一开始猜测是不是走 Docker 内部网络（比如 &lt;code&gt;Host=pgsql&lt;/code&gt;）就能免密？但 Postgres 的 &lt;code&gt;pg_hba.conf&lt;/code&gt; 机制决定了 TCP 连接必须走 &lt;code&gt;scram-sha-256&lt;/code&gt; 校验，不可能免密。&lt;/p&gt;
&lt;p&gt;通过在容器内部直接 &lt;code&gt;echo&lt;/code&gt; 环境变量，我揪出了第一个内鬼——&lt;strong&gt;未加引号的 .env 注释&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在早期的项目中，我有个&lt;code&gt;.env&lt;/code&gt;文件是这样写的：&lt;code&gt;PGSQL_APP_PASSWORD=app@pgsql # 应用通用密码&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;然后我在docker-compose.yaml中这样写了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;POSTGRES_APP_PASSWORD&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${PGSQL_APP_PASSWORD:-app@pgsql}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因为&lt;code&gt;.env&lt;/code&gt;文件没有加双引号，Docker Compose 粗暴地把后面的空格和中文注释一并吃进了环境变量里。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据库初始化时，把 &lt;code&gt;app@pgsql # 应用通用密码&lt;/code&gt; 当成了完整的密码存了进去。&lt;/li&gt;
&lt;li&gt;旧的 Auth 容器读取同样的配置，带着这串超长且带中文的密码去请求，&lt;strong&gt;两边竟然完美对上号了（负负得正）&lt;/strong&gt;！&lt;/li&gt;
&lt;li&gt;而我在命令行手动敲写干净的 &lt;code&gt;app@pgsql&lt;/code&gt;，自然被无情拒绝。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;避坑指南：&lt;/strong&gt; &lt;code&gt;.env&lt;/code&gt; 文件或 &lt;code&gt;docker-compose.yml&lt;/code&gt; 中的复杂字符串，特别是带有特殊字符的密码，&lt;strong&gt;务必养成加双引号的习惯&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="第二重陷阱被忽视的数据卷volume初始化盲区"&gt;第二重陷阱：被忽视的数据卷（Volume）初始化盲区
&lt;/h3&gt;&lt;p&gt;发现了上面的问题后，我决定修正配置，改用干净的密码 &lt;code&gt;app#pgsql&lt;/code&gt;，并重新启动容器。结果——&lt;strong&gt;依然报错！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我进入容器内部打印环境变量，确认 &lt;code&gt;$POSTGRES_APP_PASSWORD&lt;/code&gt; 已经是正确的 &lt;code&gt;app#pgsql&lt;/code&gt;，为什么还是连不上？&lt;/p&gt;
&lt;p&gt;这时候，我注意到了 &lt;code&gt;docker-compose.yml&lt;/code&gt; 里的这行代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./data:/var/lib/postgresql/data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这是 Postgres Docker 镜像的一个经典“潜规则”：&lt;strong&gt;只要挂载的目标目录（&lt;code&gt;/var/lib/postgresql/data&lt;/code&gt;）不为空，容器启动时就会直接跳过整个初始化流程（包括执行 &lt;code&gt;/docker-entrypoint-initdb.d/&lt;/code&gt; 下的脚本）。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为我之前已经运行过一次，&lt;code&gt;./data&lt;/code&gt; 目录下已经有旧数据了。所以即便我改了配置，重启了容器，数据库里的密码依然是上一次初始化时的那个带中文的“幽灵密码”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;避坑指南：&lt;/strong&gt; 在开发阶段调试初始化脚本时，如果修改了密码或初始数据库结构，&lt;strong&gt;必须清空宿主机的 &lt;code&gt;./data&lt;/code&gt; 目录&lt;/strong&gt;（&lt;code&gt;rm -rf ./data/*&lt;/code&gt;），或者进入容器用 &lt;code&gt;ALTER USER&lt;/code&gt; 强制修改密码。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="最终-bossnano-复制粘贴的背刺"&gt;最终 Boss：Nano 复制粘贴的“背刺”
&lt;/h3&gt;&lt;p&gt;好，我删除了旧数据，确信这次一定会重新初始化，&lt;code&gt;docker logs&lt;/code&gt; 也清楚地打印出执行了我的自定义脚本 &lt;code&gt;01-init-user-and-permissions.sh&lt;/code&gt;，甚至打出了 &lt;code&gt;NOTICE: User created: app&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;满心欢喜地去测试连接。
&lt;strong&gt;报错：&lt;code&gt;password authentication failed&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;那一刻我真的怀疑自己对 Docker 的认知是不是被篡改了。网络没问题、环境变量没问题、数据也清空重来了，究竟是哪里出了鬼？&lt;/p&gt;
&lt;p&gt;直到我打开那个 &lt;code&gt;01-init-user-and-permissions.sh&lt;/code&gt; 脚本，一行一行地扫，终于发现了这个极其隐蔽的致命伤：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CREATE USER &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$POSTGRES_APP_USER&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; WITH PASSWORD &lt;span style="color:#e6db74"&gt;&amp;#39;$POSTGRES_APP_PASSWO
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;RD&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;是的，你没看错。&lt;strong&gt;变量名 &lt;code&gt;$POSTGRES_APP_PASSWORD&lt;/code&gt; 被从中间腰斩，换行了！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是怎么发生的？
因为我是直接在 SSH 终端里用 &lt;code&gt;nano&lt;/code&gt; 编辑器，把另一个服务器的脚本&lt;strong&gt;复制粘贴&lt;/strong&gt;进去的。
当你的终端窗口不够宽，且 &lt;code&gt;nano&lt;/code&gt; 开启了默认的&lt;strong&gt;自动换行（Word Wrap）&lt;strong&gt;时，它会在长字符串中间插入一个真正的&lt;/strong&gt;硬回车（Hard Return）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在 Bash 的逻辑里，它找不到 &lt;code&gt;$POSTGRES_APP_PASSWO&lt;/code&gt; 这个变量（因为后面被回车截断了），于是把它解析为&lt;strong&gt;空字符串&lt;/strong&gt;。
最终，数据库真的成功执行了这条 SQL，只不过它执行的是：
&lt;code&gt;CREATE USER &amp;quot;app&amp;quot; WITH PASSWORD '';&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;密码是空的！&lt;/strong&gt; 这就是我用正确的密码死活连不上的终极原因。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="总结与最佳实践"&gt;总结与最佳实践
&lt;/h3&gt;&lt;p&gt;这三个坑叠在一起，节目效果直接拉满。为了防止自己（或者正在看文章的你）再次踩坑，总结以下几条开发铁律：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;终端编辑器防身术：&lt;/strong&gt;
如果你要在 Linux 终端下用 &lt;code&gt;nano&lt;/code&gt; 粘贴长代码或长配置，&lt;strong&gt;永远记得带上 &lt;code&gt;-w&lt;/code&gt; 参数&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nano -w script.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这会禁用自动换行功能，保命必备。更好的做法是使用 VS Code 的 Remote SSH 插件，直接修改服务器文件，告别终端剪贴板折磨。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;环境变量 密码规范：&lt;/strong&gt;
&lt;code&gt;.env&lt;/code&gt;密码和包含特殊字符的变量，用双引号包起来。行内注释最好另起一行写。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 应用通用密码&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PGSQL_APP_PASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;app@pgsql&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;数据库调试三板斧：&lt;/strong&gt;
当碰到 Docker DB 密码玄学时，直接在宿主机用超管权限查岗，一秒定音：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 检查数据库到底吃进去了什么配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec -it pgsql /bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo $POSTGRES_APP_PASSWORD
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 直接用环境变量强制连接测试&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PGPASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$POSTGRES_APP_PASSWORD&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; psql -h 127.0.0.1 -U app -d postgres
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;服务器运维就是这样，有时候折磨你两天的并非什么底层内核级 Bug，仅仅是一个看不见的换行符。记录下来，就当是给以后的自己提个醒吧！&lt;/p&gt;</description></item></channel></rss>