Ryan

Docker Compose 的 $$ 陷阱:官方文档摘抄

Compose 会在交给 Docker 之前先做一轮变量插值,所以配置里任何字面的 $ 都必须写成 $$。一条规则解释三种翻车现场。

评论
Docker Compose 的 $$ 陷阱:官方文档摘抄

Docker Compose 在读取 compose.yml 时会先做一轮变量插值,再把结果交给 Docker。这个「先于 Docker 的一轮预处理」是所有 $ 相关诡异现象的根源——尤其是当你的配置里本来就需要一个**字面意义的美元符号**时。

一、插值支持哪些写法

官方文档列出的语法一共这几档:

  • $VARIABLE 或 ${VARIABLE}:替换为环境变量值;
  • ${VARIABLE:-default}:未设置或为空时用 default;
  • ${VARIABLE-default}:仅在未设置时用 default(空字符串不算);
  • ${VARIABLE:?err} / ${VARIABLE?err}:缺失时直接报错中止,适合强制必填项;
  • $$:转义,输出一个字面的 $。

变量来源按优先级大致是:宿主机 shell 环境 → .env 文件 → 文件里显式写的 environment。

二、核心那条:$$ 表示字面的 $

用 $$ 可以让 Compose 跳过插值,把它当成一个普通的 $ 字符交给容器。

反过来说,如果你在配置里直接写了一个 $,Compose 就一定会尝试把它当成变量开头——后面跟的不是合法变量名时,轻则被替换成空串,重则直接报插值错误、整个栈起不来。

三、三种典型「中招」场景

1)密码/令牌里带 $

最经典。任何以 $ 开头的令牌或哈希,如果不转义,会被 Compose 吃成空值。表现是「明明写在配置里,容器里却是空的」。

2)$ 出现在哈希串中间

# 错误:$argon2id$v=19$m=... 会被逐段当成变量
# 正确:每个 $ 都写成 $$
ADMIN_TOKEN=$$argon2id$$v=19$$m=65536,p=4,t=3$$...

Argon2 的 PHC 字符串天然就是一堆 $ 分隔的字段,所以只要用它做凭据,就必然要处理转义。

3)命令里需要 shell 变量

写进 command 或 entrypoint 的 $HOME、$${HOSTNAME} 之类,如果本意是让容器里的 shell 去展开,就必须用 $$ 拦下 Compose 这一层——否则在宿主机侧就被替换掉了,容器拿到的是宿主机环境的值。

command: /bin/sh -c 'echo 容器内视角: $$HOSTNAME'

四、出处

  • Docker Docs — Interpolation(Compose 环境变量)
    https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/

五、在自家服务器上的印证

这不是理论问题——本站的密码管理器在换管理令牌时就实打实踩过:

新令牌改用 Argon2id PHC 格式,形如 $argon2id$v=19$m=65536,p=4,t=3$盐$哈希。第一版直接原样写进 compose.yml,结果 Compose 把每个 $ 后面的字段当成变量名去解析,容器里的令牌根本不是想要的值。

改法就是官方文档那一行:把每个 $ 换成 $$。改完重启,「验证新令牌 → 通过 / 验证旧令牌 → 失败」,一次到位。

一条经验:只要 compose.yml 里出现 $,先问自己一句「这个 $ 是给 Compose 看的,还是给容器看的?」——答案决定了它要不要写成 $$。

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者