Dockerfile 与 compose 语法错误速查:以报错行号为锚
更新时间:2026-10-24。FAQ 速查页,面向
docker build一上来就failed to solve、docker compose up直接 YAML 解析报错的同学。这类报错有个共同的好脾气:一定带行号——dockerfile parse error on line 7、yaml: line 15: could not find expected ':',语法层的问题从来不会藏在深处。坏消息是行号指的位置常是「被发现的位置」而非「犯错的位置」(比如续行符断链,报错在下一行)。病灶收敛在六根轴上:Dockerfile 指令拼写、续行符与注释断链、compose YAML 缩进错位、version 字段与 schema 过时、环境变量插值失败、引号与转义踩坑。构建能过但运行期出问题(路径、权限、端口)分别看 docker 挂载与 COPY 路径不存在速查 与 docker 权限被拒速查。
本文要回答的问题
unknown instruction: CPOY这种报错,为什么有时拼写对了也报?- 报错行号指的行明明没问题,问题在哪?
- compose 文件
version: "3.8"这行现在还要不要写? ${VAR}插值报variable is not set,.env 文件明明写了?- 为什么同样的 compose 文件,docker-compose 能跑、
docker compose报错(或反过来)?
一、报错原文与典型现象
语法类报错分 Dockerfile 与 compose 两个家族(路径以 /proj/demo 占位):
# 家族一:Dockerfile 解析失败
$ docker build -t myapp /proj/demo
[+] Building 0.1s (2/2) FINISHED
ERROR: failed to solve: dockerfile parse error on line 7:
unknown instruction: CPOY# 家族二:compose YAML 解析失败
$ docker compose -f /proj/demo/compose.yaml up
yaml: line 15: could not find expected ':'
# 或插值失败:
$ docker compose up
WARN[0000] The "DB_HOST" variable is not set. Defaulting to a blank string.# 家族三:schema 校验失败(v2 新报错风格)
$ docker compose up
validating /proj/demo/compose.yaml: services.web.ports.0
must be a string or number, got null读现象抓两个锚点:报错自带的行号——先在编辑器里跳到那一行,但记住解析器是「读到这行发现不对」,真正的错可能在上一行(续行符断了、上一行少引号);报错的英文措辞——unknown instruction 指指令层、could not find expected ':' 指 YAML 结构层、must be a string or number 指值的类型层,三层各有不同的修法,别混着猜。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| unknown instruction: XXX | 指令拼写错 / 行首有空白 | 对照官方指令表逐字母核 | 改拼写,指令顶格写 |
| 报错行内容正常 | 上一行续行符反斜杠断链 | 看上一行行尾 | 行尾只留一个反斜杠,后无空格 |
| could not find expected ':' | YAML 冒号后没空格或缩进错位 | 数报错行缩进空格数 | 统一 2 空格缩进,冒号后加空格 |
| mapping values are not allowed | 字符串里冒号未引号包裹 | 报错行找第二个冒号 | 含冒号的值加引号 |
| version 字段警告/报错 | compose v2 已废弃 version 字段 | 用 docker compose config 校验 | 删掉 version 顶层字段 |
| variable is not set 警告 | .env 未加载或变量名不一致 | compose config 看插值结果 | .env 放对位置或用 --env-file |
| must be a string or number | ports 写成 8080:08080 被 YAML 解析 | config 看解析后的值 | ports 一律加引号写成字符串 |
| Tab 缩进报错 | YAML 禁止 Tab | 编辑器显示不可见字符 | 全部换空格 |
三、排查决策图
这张图是标准排查路线:红是报错现象与命中的根因,蓝是排查动作,绿是验证通过,橙是修复,紫是防复发沉淀。
读这张图抓住第一刀:别急着改,先用 docker compose config(或 buildx 的 --check)让解析器把「它理解的样子」打印出来——语法层的问题最怕凭感觉改,改完一处引出三处。config 命令会把插值结果、缩进结构全部展开,对不上预期的地方就是病灶。
四、逐条排查展开
unknown instruction:九成是拼写,一成是排版。 CPOY、EXPOS、FORM(想写 FROM)这类手滑一眼能认;隐蔽的是排版问题——指令前面带了空格或 Tab(Dockerfile 指令必须顶格)、Windows 文件带了 BOM 头(首行 FROM 被污染成不可见字符 + FROM)、以及把 shell 续行写法带进指令名。复核姿势:
grep -nP '^\s+\S' /proj/demo/Dockerfile # 找行首有空白的内容行
file /proj/demo/Dockerfile # 看是否带 BOM 或 CRLFCRLF 行尾(Windows 编辑的产物)还会让反斜杠续行失效——反斜杠后面跟了 \r,解析器认为续行中断。
续行符断链是「报错行无辜」的头号来源。 多行 RUN 命令里,某行行尾的反斜杠后面多了一个空格、或被编辑器自动去掉了,解析器就把下一行当新指令读——报错落在下一行,错在上一行行尾。排查口诀:报错行正常就往上看一行;预防靠编辑器把行尾空白显示出来。
YAML 缩进错位是 compose 报错的主力。 YAML 只认空格、且同级必须对齐,could not find expected ':' 与 mapping values are not allowed 多半是两种形态:一是缩进不齐(services 下的 web 比 environment 多缩了一级之类);二是字符串里含冒号没加引号(TZ: Asia/Shanghai 没事,但 - 8080:80:extra 这类值会把解析器绕晕)。统一规矩:全文件 2 空格缩进、禁 Tab、值里含冒号/特殊字符一律引号。
ports 的类型坑值得单拎。 - 8080:80 不加引号时,YAML 可能把它按六十进制数解析(老的 YAML 1.1 行为)或直接报类型错——compose v2 的报错 must be a string or number 常源于此。铁律:ports 永远写成字符串 - "8080:80";同理 environment 里的布尔语义值(on/off/yes/no)也加引号,避免 YAML 1.1 把它们解析成布尔。
version 字段已经退役。 用 v2 的 docker compose(注意中间是空格)时,顶层 version: "3.8" 只会收获一条 the attribute version is obsolete 警告——v2 不再按 version 选择 schema,直接删掉这一行。反过来,老环境还在用 Python 版 docker-compose(带横杠)时新语法特性可能不认,报 Unsupported config option——这时要么升级 CLI,要么按老版文档回退写法。两个命令并存时先 docker compose version 与 docker-compose version 各看一眼,确认自己在跟哪个解析器对话。
变量插值失败的排查有现成工具。 ${DB_HOST} 报 variable is not set,按顺序查:.env 文件是否在 compose 文件同目录(默认只从那里读);变量名是否逐字母一致(下划线与横杠不分是常见手滑);shell 里是否有同名环境变量抢占了优先级(shell 环境 > .env)。终极复核:
docker compose -f /proj/demo/compose.yaml config
# 展开后的完整配置:插值结果、端口、挂载全部所见即所得需要多套环境时用 --env-file /proj/demo/.env.prod 显式指定,别依赖默认查找路径。想给变量默认值,写法是 ${DB_HOST:-localhost},冒号与减号一个都不能少。
Dockerfile 的静态检查可以前置。 buildx 带的 docker build --check(新版本)或 docker buildx build --call=check 能在不真正构建的情况下跑 lint,把拼写、弃用指令、FROM 大小写不一致(from 与 AS 混用的警告)一次列全;配合 hadolint 这类 linter 进编辑器,语法错在写的时候就亮红灯,比构建时报错省一个来回。
五、规范做法与预防
- 编辑器层拦截:装 YAML/Dockerfile 插件,开 Tab 可见、行尾空白可见;保存即 lint。
- CI 必跑 config:
docker compose config -q作为流水线第一道卡口,语法错不过夜。 - ports 一律字符串:团队规范写死,从根上消灭六十进制坑。
- .env 进 gitignore 但留模板:提交
.env.example,README 写明复制动作。 - 删掉 version 字段:新文件不写,老文件见到就删。
- Dockerfile 顶格纪律:指令行首无空白;跨平台协作强制 LF 行尾(
.gitattributes里*.Dockerfile text eol=lf)。
这一篇在知识体系里的位置
本篇是 docker 排错速查簇的构建期语法篇,定位「还没跑起来就倒在解析器手里」:语法过了、构建期找不到文件看 docker 挂载与 COPY 路径不存在速查;镜像拉不下来看 docker 镜像拉取限流速查;构建成功后运行期的端口、权限、内存问题分别看 docker 容器端口连接被拒速查、docker 权限被拒速查 与 docker 容器 OOMKilled 速查;compose 里环境变量与 locale 引发的运行期中文乱码看 docker 容器内中文乱码速查。报错行号锚定、compose config 复核、编辑器 lint 前置,是配置文件排错的通用底盘。
一句话总结
Dockerfile 与 compose 语法错永远以报错行号为锚,但记住「报错行是发现地不是犯罪现场」——行内容正常就往上看一行(续行符反斜杠后多了空格、CRLF 行尾断链);unknown instruction 查拼写与行首空白,YAML 的 could not find expected ':' 查缩进与冒号后空格,ports 永远加引号写成字符串躲六十进制坑,v2 里 version 字段直接删掉。插值报错用 docker compose config 把展开结果打印出来对账,比凭感觉改可靠十倍。编辑器 lint + CI 里 compose config -q 卡口前置,这类报错基本进不了构建环节。