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

Docker 挂载配置文件报 not a directory:为什么宿主机多了一个同名目录

配置文件挂载时报 not a directory,核对 Compose 最终路径、源文件类型与容器目标,用 create_host_path: false 阻止误建目录,再重建和验证受影响的服务。

Compose 启动失败,报错里出现 not a directory 或 Are you trying to mount a directory onto a file (or vice-versa)?。检查宿主机时,原本放配置文件的位置却成了名叫 default.conf 的目录。

这时先核对挂载两端的路径和类型。文件名里的 .conf、.yaml 不会让 Docker 把路径自动识别成文件。短语法挂载的源路径不存在时,Compose 会在宿主机创建一个目录;容器内的目标若是文件,就可能发生类型冲突。

下面以 Linux 宿主机、当前 docker compose 和 Nginx 配置文件为例。服务名用 web,请换成自己的服务名;文中的 Compose 片段只替换该服务的挂载项,保留原有镜像、端口和其他配置。

先看 Compose 最终要挂载什么

变量展开、额外的 Compose 文件和项目目录,都可能让实际路径与预期不同。先确认最后提交给 Docker 的配置。

在平时启动项目的位置执行:

docker compose version
docker context show
docker compose config

config 会合并配置、展开变量,并显示规范化后的服务定义。找到 web 的 volumes,记下实际的 source、target 和 type。--quiet 只检查配置,不输出路径,排查时不要用它代替完整结果。

如果平时用多个文件启动,检查时也带上同样的文件、顺序、项目名和环境文件。例如:

docker compose -f compose.yaml -f deploy/compose.prod.yaml config

这种合并方式下,相对路径以第一个 -f 指定的基础文件为基准,不以覆盖文件所在目录为基准。deploy/compose.prod.yaml 里写的 ./config/default.conf,未必指向 deploy/config/default.conf。

配置结果可能含有展开后的密码和环境变量。需要请别人协助时,只提供相关挂载项,并先检查敏感内容。

在运行 Docker 的那台机器上确认源路径

假设最终的源路径是 /srv/example/config/default.conf,在 Docker 宿主机上检查:

ls -ld -- /srv/example/config /srv/example/config/default.conf
test -f /srv/example/config/default.conf && printf '%s\n' '源路径是文件'
test -d /srv/example/config/default.conf && printf '%s\n' '源路径是目录'

/srv/example 是示例目录,执行前换成 config 输出里的实际路径。ls -ld 看路径本身;普通文件通常以 - 开头,目录以 d 开头。如果显示为符号链接,还要检查它指向的对象及其是否存在。

根据源路径的检查结果决定下一步:

  • 路径不存在:先把有效配置文件放到这个准确位置,再设置 create_host_path: false,让以后缺文件时直接失败。
  • 路径是目录:先确认目录的用途和内容。只有误创建的空目录才按下节处理,不要直接删除同名路径。
  • 路径是普通文件:继续核对容器目标、它的父目录及其他挂载。仅凭 not a directory 不能认定宿主机缺文件。

远程 Docker 尤其容易查错机器。绑定挂载的源路径来自 Docker daemon 所在的宿主机。电脑本地有文件,不代表远端服务器也有。docker context show 显示当前 context 名称;还应核对实际连接的 endpoint,以及是否通过 DOCKER_HOST 或命令行参数指定了远端。

Docker Desktop 则有自己的宿主机文件共享机制。若文件确实存在且类型正确,错误却涉及 /run/desktop/、WSL 或共享挂载,应继续检查 Desktop 的共享路径和所在环境,不能直接按普通 Linux 的缺文件情况处理。

同名目录已经出现,怎样恢复文件

先保存 Compose 配置和有效配置文件的备份。若相关容器还在运行,安排维护窗口并停止受影响的服务,再处理路径。

只有确认 default.conf 是误创建的空目录、且没有其他用途时,才移除它:

rmdir -- /srv/example/config/default.conf

rmdir 遇到非空目录会失败。此时停下来查看内容,不要换成 rm -rf。将真实的配置文件从受控副本或备份恢复到这个位置,再用 test -f 确认。创建一个空文件只能改变路径类型,不能代替应用所需的配置。

如果源文件正确,仍报同样的错,就检查 target:它是否应该是文件,父目录是否被另一个挂载覆盖,镜像里的目标是否实际上是目录。一个目录路径不能靠加上文件扩展名变成文件。已有容器还可以通过下面的检查确认记录中的挂载两端:

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

第一条命令通过 -q 输出 web 服务的容器 ID,并通过 -a 包含已停止的容器。将 CONTAINER_ID 换成查到的 ID,核对 Source、Destination 和 Type;如果容器没有创建成功,就先以最终 Compose 配置和源文件检查为准。docker inspect 支持按对象类型检查,避免同名对象造成混淆。

配置文件挂载应在缺文件时直接失败

下面的短语法会在源路径缺失时创建目录:

volumes:
  - ./config/default.conf:/etc/nginx/conf.d/default.conf:ro

对必须事先存在的配置文件,更合适的写法是:

volumes:
  - type: bind
    source: ./config/default.conf
    target: /etc/nginx/conf.d/default.conf
    read_only: true
    bind:
      create_host_path: false

create_host_path: false 禁止为缺失的源路径自动创建目录;它不会生成配置内容,也不会把已有目录转换成文件。read_only: true 限制容器写入这份配置,同样不负责修复路径类型。长语法里仅写 type: bind 还不够,create_host_path 的默认值仍是 true。

如果工具不接受这个字段,先核对实际使用的 Compose 版本,不要通过删除该字段来掩盖源文件缺失。本文使用当前 docker compose,旧 Python docker-compose 要另外核对支持情况。

容器目标位于根目录,并不自动构成挂载错误。应核对源路径是否确实为文件、目标类型是否匹配,以及 create_host_path 的设置,不能靠把目标换到 /etc 就认定原因已经查清。

改完挂载,重建服务并检查应用

修改 YAML 后,单纯 docker compose restart 不会应用新的 Compose 配置。修改环境变量后 restart 仍使用旧值也是这条规则的一种表现。

确认配置文件已经存在、应用依赖已经运行,并评估重建影响后,只重建受影响的服务:

docker compose up -d --no-deps --force-recreate web
docker compose ps -a web
docker compose logs --tail=50 web

同样要沿用原来的 -f、环境文件和项目名。--no-deps 不负责启动依赖;--force-recreate 会重建该服务的容器,产生短暂中断,容器可写层里没有持久化的数据也要事先保存。重建时会保留挂载卷。本问题不需要删除数据卷。

服务启动后,再检查 Mounts 是否指向正确文件。对标准 Nginx 容器,可执行:

docker compose exec web nginx -t

nginx -t 检查配置语法,并尝试打开配置引用的文件。它通过后,还要访问自己的业务入口,确认请求由预期配置处理。其他应用应使用各自的配置校验或健康检查方法。

如果挂载错误消失,接着出现配置语法或权限错误,按新日志继续处理。不要为了让容器启动而关闭文件检查或放宽所有权限。需要回退时,恢复保存的 Compose 定义和有效配置文件,再按原启动参数重建同一服务;也不要把误创建的目录恢复成配置文件路径。

如果容器已能启动,源文件和目标类型也正确,只是改了宿主机配置后容器仍读到旧内容,接着检查单文件绑定挂载与文件替换造成的旧配置。