问题背景
Docker Compose 里经常会把单个配置文件 bind mount 到容器中:
1 | services: |
这样写很方便:宿主机改配置,容器里也能读到新内容,不需要重新构建镜像。但它有个隐蔽问题:某些编辑方式会让宿主机文件和容器内文件断开同步。
现象
容器启动后,最开始一切正常。后来你用 vim、VSCode 或 sed -i 修改 config.json,可能会发现:
- 宿主机上的
config.json已经更新 - 容器里的
/app/config.json仍然是旧内容 - 容器继续使用旧配置
- 两边对这个文件的修改不再互相可见
这个问题看起来像同步失效,但本质上是挂载点还指向旧文件。
原因
单文件 bind mount 的关键点是:Docker 挂载的是当时那个文件对象,而不是永远跟随这个文件名。
1 | ./config.json inode A -> /app/config.json |
很多编辑器保存文件时,并不是直接在原文件上修改,而是走类似这样的流程:
1 | 写入 config.json.tmp |
保存完成后,宿主机路径 config.json 指向了新的 inode:
1 | 宿主机: ./config.json -> inode B |
容器仍然挂着旧文件对象,于是宿主机和容器里的文件就“分离”了。
为什么隐蔽
它不是每次修改都会触发,而是取决于工具的保存方式:
| 操作 | 是否可能更换 inode |
|---|---|
vim 保存 |
可能 |
| VSCode 保存 | 可能 |
sed -i |
可能 |
cp 覆盖 |
可能 |
echo >> file |
通常不会 |
所以它经常表现成“之前都正常,某次修改后突然不生效”。
推荐做法
不要挂载单个文件:
1 | volumes: |
推荐把配置放进目录,挂载整个目录:
1 | volumes: |
项目结构可以这样放:
1 | project/ |
程序读取:
1 | /app/data/config.json |
这样即使 data/config.json 被编辑器替换成新 inode,容器看到的仍然是同一个挂载目录下的新文件。
Compose 示例
1 | services: |
配置、日志、运行数据都按目录挂载,后续新增文件也不需要再改 compose。
总结
单文件 bind mount 的坑在于:文件名没变,不代表底层文件对象没变。编辑器或命令一旦用 rename 覆盖原文件,容器可能还挂着旧 inode,导致宿主机和容器内的文件不再同步。
实际项目里,优先挂载目录:
1 | 不推荐: ./config.json:/app/config.json |
单文件挂载只适合非常明确、不会被运行时频繁编辑替换的场景。