DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

Docker Compose 设置卷的 name 后,仍报 refers to undefined volume

Docker Compose 的卷已设置 name,却报 refers to undefined volume,常见原因是服务挂载引用了实际卷名,而非顶层 volumes 的配置键。保留固定内部键和动态 name,并核对真实挂载,避免接到新空卷。

Compose 文件里已经声明了卷,并通过 name 给它设置了实际名称,启动却仍在项目校验阶段失败:

service "redis" refers to undefined volume redis-dev: invalid compose project

这类错误容易出现在按环境拼接卷名的配置中。服务挂载引用的是 Compose 顶层 volumes 下的键,不是该键内部 name 字段的值。即使 Docker Engine 上已经存在 redis-dev,也不能补上 Compose 模型中缺失的引用。

下面针对现代 Docker Compose 插件与命名卷配置。报错里的服务名、卷名会随项目变化;如果使用的是主机目录绑定挂载,应另查路径写法,不要为了通过校验就把目录改成命名卷。

区分 redis 和 redis-dev 分别在哪里使用

下面是能展示问题的简化结构,省略了业务配置:

services:
  redis:
    image: redis:7
    volumes:
      - redis-${APP_ENV}:/data

volumes:
  redis:
    name: redis-${APP_ENV}

当 APP_ENV=dev 时,服务引用会变成 redis-dev,但顶层只定义了 redis。Compose 找不到名为 redis-dev 的卷配置项,因此报 undefined volume。name: redis-dev 是给引擎上的卷指定名称,不会再创建一个同名的 Compose 配置键。

这里三个名称的位置不能互换:

位置 示例 作用
顶层卷配置键 volumes.redis Compose 文件内部引用的标识
服务挂载的源 redis:/data 中的 redis 指向顶层卷配置键
卷的 name 属性 redis-dev Docker Engine 上实际使用的卷名

变量展开后,Compose 仍需找到对应的卷配置键。也不要尝试把顶层键写成 redis-${APP_ENV}: 来配合:Compose 的插值作用于 YAML 值,不作用于这些映射键。保持内部键固定,把需要变化的名称放在 name 的值中即可。

保留动态实际卷名,只修正服务引用

针对上面的结构,完整的最小示例为:

services:
  redis:
    image: redis:7
    volumes:
      - redis:/data

volumes:
  redis:
    name: "redis-${APP_ENV:?APP_ENV must be set}"

这里将服务引用改为 redis:/data。顶层仍通过 name 选择实际卷名;${APP_ENV:?...} 要求变量已经设置且非空,避免遗漏环境标识时悄悄使用不符合预期的卷名。

redis:7 仅用于说明配置结构。修复已有部署时保留原来确认过的镜像、端口、认证、网络和持久化选项,只修改错误引用及确有需要的变量检查。不要用这份最小示例覆盖整份生产配置。

name 指定的名字按原样使用,不会自动加上 Compose 项目名前缀。因此,两个项目只要在同一个 Docker Engine 上配置了同一个 name,就可能指向同一个实际卷。环境变量的取值既要符合卷名要求,也要符合预期的数据隔离边界,不能只检查字符串能否拼接成功。

已有外部卷也仍然通过内部键引用

如果卷由其他流程创建和管理,且 Compose 只负责使用它,可以保留相同的内部引用,把顶层定义改成下面的片段:

volumes:
  redis:
    external: true
    name: "redis-${APP_ENV:?APP_ENV must be set}"

服务中仍然写 redis:/data。external: true 表示 Compose 不创建这个卷;指定的实际卷必须存在,否则会出现另一个“外部卷不存在”的错误。它不会让未定义的内部引用自动变得合法。

不要仅为消除 undefined volume 而切换 external。它决定卷的生命周期管理方式,应由谁负责创建、备份和移除来决定。使用外部卷时,除 name 外,不要再混入 driver 等用于创建卷的属性。

先验证模型,再确认接上的确实是原数据

先保存原 Compose 文件和环境设置。下面假定项目显式使用 compose.yaml、.env.dev,其中已经设置 APP_ENV=dev。文件名是示例;如果平时还使用多个 -f、-p 或其他环境参数,以下每一步都要沿用相同参数:

docker compose --env-file .env.dev -f compose.yaml config --quiet

这一步校验合并并完成变量解析后的模型。通过说明原来的未定义卷引用已经解决,不表示卷内容正确,也不表示 Redis 已经可用。仍然失败时,核对实际加载的文件中是否定义了 volumes.redis,以及服务挂载源是否恰好写成同一个键。

需要检查解析后的名称时,可在可信终端查看完整模型:

docker compose --env-file .env.dev -f compose.yaml config

Shell 中已导出的同名变量优先于 --env-file,所以文件里写了 APP_ENV=dev 仍要看最终解析值。重点核对服务挂载源指向 redis、目标为 /data,顶层卷的实际名称为预期的 redis-dev。完整输出可能包含其他服务的敏感配置,不要直接粘贴到公开讨论或构建日志。

对已有数据的部署,在更新容器前检查目标实际卷:

docker volume inspect redis-dev

核对 Name、驱动、标签以及存储位置与原部署记录是否一致。卷名存在只能证明存在这个对象,不能证明其中就是正确的数据。数据库或持久化服务还需要按自身方式完成可恢复的备份;不要直接复制正在写入的数据目录来冒充一致性备份。

确认名称和数据归属后,再在允许的维护窗口更新服务:

docker compose --env-file .env.dev -f compose.yaml up -d redis
docker compose --env-file .env.dev -f compose.yaml ps -q redis

up 可能停止并重建配置发生变化的容器,不是只读校验。把第二条返回的目标容器 ID 用于挂载检查:

docker container inspect --format '{{json .Mounts}}' CONTAINER_ID

CONTAINER_ID 替换为实际容器 ID。检查 /data 对应的挂载 Type 为 volume,Name 为预期的实际卷名,再通过应用的读取流程确认原有数据。只看到容器运行,不能证明它没有误接一个新空卷。

更改名称不会迁移卷内数据

修正内部引用通常无须给实际卷改名。如果顺手改变 name、项目名或用于生成名称的变量,Compose 可能转而使用另一个卷;由 Compose 管理且目标不存在时,还可能创建新卷。应用看起来“数据清空”,首先应核对挂载对象,而不是立刻初始化或恢复覆盖数据。

需要回退时,保留新旧卷,恢复最近一份可用配置及相应环境设置,确认它指向原来的实际卷后再重新应用。带有错误引用的文件只能留作比较,不能当作可用回退方案。若错误挂载期间已经发生写入,要分别保全两份数据,再按应用的恢复流程处理;配置回退不会自动合并数据。

不要运行 docker compose down -v、删除卷或执行卷清理来修复这个名称错误。down -v 会移除其管理范围内声明的命名卷等资源,而修正 Compose 内部引用并不需要删除数据。