DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Docker 单文件挂载后,容器读到的配置没有更新

宿主机保存了配置,Docker 容器仍读到旧内容。单文件绑定挂载遇到文件替换时可能保留旧对象;通过挂载路径、内容摘要和 inode 缩小范围,再选择重建容器或改为专用目录挂载。

宿主机上的配置已经保存,容器里同一路径却还是旧内容。给应用发送 reload 信号也没用,甚至把配置重新检查一遍,看到的仍是修改前的版本。

如果挂载的是一个文件,编辑器或部署工具又采用“写临时文件,再替换原路径”的保存方式,就可能出现这种情况:宿主机上的文件名没变,但它已经指向一个新文件对象,容器的单文件绑定挂载仍连接着旧对象。

下面针对原生 Linux Docker Engine、本地文件系统上的普通 bind mount,使用 Compose V2 命令。Docker Desktop 的文件共享、网络文件系统和 Kubernetes 的配置投射各有额外机制,不宜只凭表面现象套用这个结论。示例服务名为 app,路径也需要按实际部署替换。

先确认是文件没更新,还是应用没重新读取

先查看目标服务的容器 ID 和实际挂载:

docker compose ps -q app
docker inspect --type container --format '{{json .Mounts}}' CONTAINER_ID

将 CONTAINER_ID 替换为第一条输出的目标 ID;有多个副本时逐个核对。关注对应条目的 Type、Source、Destination 和 RW。本问题要求该项确实是 bind,而且源和目标指向单个文件。容器记下的源路径正确,并不能保证该路径后来没有被新文件替换。

假设宿主机源文件为 /srv/example/config/app.conf,容器目标为 /etc/example/app.conf,分别读取内容摘要:

sha256sum /srv/example/config/app.conf
docker compose exec app sha256sum /etc/example/app.conf

两条命令分别读取宿主机和容器中的文件。示例要求两边都有 sha256sum;精简镜像没有这个工具时,用已有的等价文件读取工具比较,不要把“命令不存在”误判成挂载故障,也不必临时改装生产容器。

两个摘要相同,说明这次读取的文件内容一致。应用仍用旧设置,应继续检查它实际加载的文件路径、启动参数以及 reload 是否成功。如果摘要不同,才继续检查挂载和保存方式。含凭据的配置不要直接贴到终端录屏或公开日志。

远程 Docker 要特别注意:Source 属于 daemon 所在机器。只改电脑本地的同名文件,不会修改远程容器使用的源文件。检查和编辑都应落在同一台实际宿主机上。

文件名没变,inode 可能已经变了

在 Linux 上,路径是找到文件的一种方式。单文件 bind mount 建立后,不会每次都根据宿主机的这个文件名重新选择对象。

一些保存流程会先写出完整的新文件,再通过重命名替换原路径。新的路径查找得到新文件,已有挂载仍可能保留旧文件。反复让应用重新打开容器内那个挂载点,也就仍然读到旧配置。

可以在正常编辑前后记录宿主机文件信息,同时查看容器中的文件。以下 stat 格式适用于 GNU stat:

stat -c '%d:%i %s %y %n' /srv/example/config/app.conf
docker compose exec app stat -c '%d:%i %s %y %n' /etc/example/app.conf

其中 %d:%i 表示设备号和 inode,后面是大小、修改时间与文件名。若宿主机保存后 inode 改变,而容器内容和文件标识仍停在原对象,这是重要线索。要结合前后的记录、内容摘要和挂载路径判断,不能仅凭大小相同、时间接近或一个孤立的 inode 数字下结论。

这个问题通常与保存动作有关,不意味着所有 Vim 保存、所有配置管理工具都一定替换 inode。同一个编辑器在不同配置、权限和文件类型下,也可能采用不同写法。

容器内直接替换挂载点时出现 Device or resource busy,则是另一种操作被拒绝。它不能证明宿主机替换源文件后,容器会自动切换到新文件,也不能据此认定某个 Docker 版本已经修复了同步问题。

临时恢复:重新建立目标服务的挂载

如果这次只需要让服务读到当前配置,可以在维护窗口重建目标容器,让 Docker 重新解析源路径并建立挂载。

操作前保留 Compose 定义、当前宿主机配置和仍在使用的有效旧配置,确认镜像版本及其他设置没有夹带变化。重建会中断该服务;只留在容器可写层中的数据会丢失,需要先保存。挂载卷虽然可以保留,也不代替业务备份。

docker compose config -q
docker compose up -d --no-deps --force-recreate app

第一条检查成功后再执行第二条。沿用部署时相同的 -f、项目名、环境文件及其他必要参数,避免操作到另一个项目。--no-deps 不会替你启动缺失的依赖服务,--force-recreate 即使 Compose 定义未变化也会重建目标容器。

重建后再次比较内容摘要,再检查应用启动日志及实际行为。仅让应用 reload 不会重建 Docker 挂载;修改了 Compose 挂载定义时,也不能只用 docker compose restart 代替重新创建。

重建并未改变单文件挂载的机制。如果下一次保存又替换了源文件,同样的问题仍可能重现。

经常更新配置时,挂载一个稳定的专用目录

如果配置需要经常修改,更合适的结构是挂载专用配置目录,让应用通过目录中的文件名读取内容。前提是目录本身保持不变,只更新其中的文件;如果部署工具连整个源目录都替换了,仍需重新检查挂载对象。

例如,原来服务中有下面这一项:

volumes:
  - ./config/app.conf:/etc/example/app.conf:ro

在确认 ./config 已经包含应用所需的全部配置,且 /etc/example 可以由这份目录完整提供后,将该挂载项替换为:

volumes:
  - type: bind
    source: ./config
    target: /etc/example
    read_only: true
    bind:
      create_host_path: false

这是现有服务下的 volumes 片段,不是独立 Compose 文件。移除冲突的旧单文件挂载,保留其他无关卷;不要同时把新目录和旧文件叠挂在相同配置路径上。相对路径按项目实际使用的 Compose 文件解析,修改前应确认最终源目录。

目录挂载会遮住镜像原本位于目标目录中的内容,不会自动把那些默认文件复制出来。若镜像里还有必须保留的配置,应先准备完整目录;无法完整接管时,可改用应用支持的独立配置目录,并相应调整其加载路径,而不是把宿主机整个 /etc 挂进去。

专用目录中只放该容器需要的文件。read_only: true 限制容器写入,但不会阻止宿主机更新源文件,也不会让容器自动重新加载应用配置。create_host_path: false 则让源目录缺失时直接失败,避免误创建空目录后遮住镜像配置。

完成应用自身的配置校验,再按前面的步骤重建目标服务。以后替换目录内的配置文件时,容器重新按路径打开文件可以看到新对象;应用若一直持有旧文件描述符或缓存了解析结果,仍需要按它支持的方式重新加载。

保留单文件挂载时,要控制保存方式

如果部署要求只能暴露一个文件,就需要约定更新策略,例如每次替换配置后重建目标容器,或者采用不会替换文件对象的写入流程。

Vim 的备份写入策略可以在当前缓冲区查看:

:setlocal backupcopy?
:set backup? writebackup?

制作备份时,backupcopy=yes 使用复制备份再覆盖原文件的方式,no 则重命名旧文件并写出新文件,auto 会按条件选择。这项设置只适用于 Vim,也不能仅凭默认值判断本次保存是否保留了 inode。

就地覆盖还存在读取半份配置的风险:写入过程中,监控程序可能立即触发 reload。不要为维持 inode,牺牲原先依赖的原子更新和校验流程。能使用稳定目录挂载时,通常更容易保留“先准备完整文件,再替换,再校验和重新加载”的发布顺序。

需要回退时,恢复保存的有效配置和原 Compose 挂载定义,再重建同一服务。随后确认容器读到的内容与预期一致,并核对应用加载结果;既不删除数据卷,也不把“容器已启动”当成配置已经生效。