Docker Compose 2.29.6 报 extra_hosts array items must be unique
Docker Compose 2.29.6 在继承或多文件合并后可能报 extra_hosts 条目不唯一。沿配置来源区分重复声明与版本缺陷,保留必要映射,并在更新插件后核对实际合并结果。
Compose 文件里明明只看到一条 extra_hosts,执行配置检查或启动命令时,却提示合并后的前两项重复:
services.app.extra_hosts array items[0,1] must be unique
如果项目还使用了 extends、公共服务定义或多个 Compose 文件,就要沿着配置来源检查。当前文件只有一条记录,不代表合并后的服务只有一条记录。Docker Compose 2.29.6 在相关合并场景中存在重复项处理问题,不能只凭这条报错断定当前文件写错了两次。
这个错误发生在 Compose 解析和校验配置时。修改宿主机 DNS、重启 Docker daemon 或删除容器,都不能消除配置模型中的重复条目。
先固定版本和配置入口
在实际运行项目的同一终端或 CI 环境查看 Compose 版本:
docker compose version
这里关注 Compose 插件版本,不能用 Docker Engine 的版本号代替。开发机升级了插件,CI 镜像或开发容器工具仍可能调用另一份安装。
按原启动命令的文件顺序检查配置。例如,项目原来使用两个文件:
docker compose -f compose.yaml -f compose.dev.yaml config
compose.yaml 和 compose.dev.yaml 是示例文件名,需替换为实际文件。原命令指定了项目名、环境文件或其他选项时,也沿用相同设置。config 会合并输入文件、处理变量并输出实际模型;其中可能含环境变量值,不要把完整结果直接贴到公开页面。
如果 config 本身就报相同错误,没有输出完整模型,这也是有效结果:失败发生在生成容器之前。此时先阅读输入文件及它们引用的公共定义,不要靠跳过校验来继续启动。
找出报错服务的 extra_hosts 来自哪里:当前服务、它通过 extends 引用的服务、后者继续引用的服务,以及其他参与合并的文件。开发工具自动生成的 Compose 文件也应纳入检查。只搜索当前编辑器打开的那个文件,容易漏掉继承来源。
完全相同的映射只保留一个定义位置
例如,公共配置已经声明了下面的映射,具体服务又重复声明一次:
extra_hosts:
- "api.internal=198.51.100.10"
这里的域名和地址都是说明结构的占位值,实际项目应保留自己的目标地址。重复指的是同一个主机名与同一个地址组合;同一个主机名配置不同地址,不应被当成相同记录全部删掉。
先保存将要修改的文件,然后把完全相同的公共映射集中在一个定义位置。下面给出修正后的两文件结构。example/app:approved-tag 是镜像占位值,必须换成项目原有镜像;这不是需要另外启动的一套服务。
common.yaml:
services:
common:
image: example/app:approved-tag
extra_hosts:
- "api.internal=198.51.100.10"
compose.yaml:
services:
app:
extends:
file: common.yaml
service: common
这两个文件放在同一目录。使用这套示例结构时,入口只有 compose.yaml:
docker compose -f compose.yaml config
extends 会读取 common.yaml 中指定服务的属性,不需要再为了加载它而把 common.yaml 加成另一个 -f 输入。外部文件中的 common 服务也不会仅因被引用就成为需要单独启动的服务。
现有项目只调整重复的映射来源,保留自己的端口、卷、环境变量和其他设置。如果公共服务的其他使用者还依赖这条映射,优先移除具体服务中的重复项;不要为了让一个入口通过校验,把所有服务都需要的公共映射删掉。
没有重复声明时,处理 2.29.6 的合并问题
如果完整输入里确实只声明了一次,但在继承或多个输入文件组合后仍出现这条错误,就不要继续删必要的主机映射。
Compose 后续修复了 extra_hosts 合并时的去重逻辑:相同映射可以去重,同名但不同地址的条目仍需保留。2.30.0 已包含相关修复;这个版本号用于判断历史修复范围,不是建议现在专门安装这个旧版本,也不表示更新版本出现同样报错时都属于同一缺陷。
按原有安装渠道,将实际使用的 Compose 插件更新到经过项目兼容性验证的维护版本。Linux 包管理安装和手工安装的更新路径不同,手工放置的插件不会自动随软件包更新。更新后,在原终端、CI 或开发工具真正调用的环境里再次执行 docker compose version,确认变化已经生效。
暂时不能更新时,可以在配置副本中调整映射的定义层级,让报错服务的合并链只接收一次所需记录,再用完全相同的入口校验。不同入口可能继承不同内容,每个受影响入口都要检查。不能把“删掉整段后不报错”当作修好,因为服务仍可能需要这些名称映射。
校验通过后核对映射,保留可回退的改动
重新运行原来的 config 命令,检查输出里目标服务的 extra_hosts:需要的每个主机名和地址都应保留,完全相同的记录不再重复,其他服务的映射没有丢失。输出可能把分隔符规范化为 =,只要主机名和地址相同,就不应把这种格式变化视为映射发生变化。
确认完整输出符合预期后,再做一次只校验、不输出配置内容的检查:
docker compose -f compose.yaml -f compose.dev.yaml config --quiet
这里沿用前面的两文件项目示例;若采用两文件 extends 示例,入口仍只写 -f compose.yaml。命令成功只代表 Compose 配置校验通过,不代表目标服务已经可达。真正变更了运行时映射时,还需按项目正常部署流程应用配置,并检查应用的名称解析和实际连接。
如果只是更新 Compose 解析工具,现有容器不会因此自动替换,不需要先删除它们。保留原插件版本记录和配置副本;调整引入其他兼容问题时,按原安装渠道恢复原插件,或恢复此次修改的文件,再检查原入口。恢复到原先有问题的组合也会恢复原报错,不能把回退配置等同于修复完成。


