Dockerfile 中多行 shell heredoc 报 unknown instruction
Dockerfile 先解析指令,shell 引号不会把多行配置归入同一个 RUN。使用 BuildKit 的 COPY 或 RUN heredoc,或把静态文件放进构建上下文。
把能在终端运行的多行命令放进 Dockerfile 的 RUN /bin/bash -c '...' 后,构建可能在配置内容的第一行报 unknown instruction: [global]。单引号看起来包住了整个脚本,Dockerfile 解析器却已经把下一行当作新的 Dockerfile 指令。
典型问题是这种结构:
RUN /bin/bash -c 'cat <<EOF > /etc/demo.conf
[global]
mode = local
EOF'
Dockerfile 必须先把输入分成指令,才会把 RUN 的内容交给容器中的 shell。shell 的引号不会自动让 Dockerfile 的物理换行变成同一条指令。错误出现在 Dockerfile 解析阶段,此时调整目标文件权限或更换 bash 路径,无法让 [global] 变成合法指令。
静态配置直接用 COPY heredoc
如果只是往镜像放一份固定文本,使用支持 heredoc 的当前 BuildKit Dockerfile 前端,直接写成 Dockerfile 自己能识别的多行文件内容。下面是可单独使用的完整 Dockerfile:
# syntax=docker/dockerfile:1
FROM alpine:3.22
COPY <<'CONFIG' /etc/demo.conf
[global]
mode = local
CONFIG
RUN cat /etc/demo.conf
COPY <<'CONFIG' 告诉前端:后面的行属于这一条 COPY,直到单独一行的 CONFIG。结束标记不要附加引号、注释或其他字符,也不要随意缩进;示例没有使用允许去掉前导制表符的 <<- 形式。
给起始标记加引号可以避免内容在构建时进行变量展开。需要原样保存 $NAME 一类占位符时尤其要明确这一点。若确实需要构建参数展开,应单独设计并核对展开规则,不能为了通过解析就去掉引号。
这里的 /etc/demo.conf 是普通演示文件,不代表任何特定软件会自动读取这个路径。实际应用应使用它真正支持的配置路径与格式。配置含密码或令牌时,不应把它直接固化进镜像层。
需要执行多条命令时,让外层 RUN 承接多行
若还要创建目录或进行其他构建步骤,使用外层 Dockerfile heredoc,再在其中使用 shell heredoc。两个结束标记要不同:
# syntax=docker/dockerfile:1
FROM alpine:3.22
RUN <<'SCRIPT'
set -eu
mkdir -p /etc/demo
cat > /etc/demo/settings.conf <<'CONFIG'
[global]
mode = local
CONFIG
cat /etc/demo/settings.conf
SCRIPT
外层 SCRIPT 决定 Dockerfile 指令在哪里结束,内层 CONFIG 决定 shell 写入文件的内容在哪里结束。示例只使用镜像默认 shell 可执行的语法,无需额外调用 bash;若自己的脚本依赖 bash 功能,则必须同时确保镜像具备该解释器,并按前端支持的方式显式选择它。
不要把整个文件机械改成每行末尾加反斜杠。普通 shell heredoc 的结束标记依赖换行,折叠这些行可能改变脚本含义,甚至导致结束标记无法识别。
构建环境不支持时,把文件放进构建上下文
# syntax=docker/dockerfile:1 适用于 BuildKit Dockerfile 前端;它不是让所有旧构建器都立即具备 heredoc 的开关。受控构建环境无法使用该前端时,可以把静态文件保存为构建上下文中的 demo.conf:
[global]
mode = local
然后使用普通 COPY:
FROM alpine:3.22
COPY demo.conf /etc/demo.conf
RUN cat /etc/demo.conf
这两个文件应位于所选构建上下文中,且 demo.conf 不被 .dockerignore 排除。此方案把文本文件交给 COPY,避免让配置内容参与 Dockerfile 指令解析。
验证文件内容,再接回实际构建
可先在独立测试目录构建上述最小示例:
docker build --progress=plain -t demo-heredoc:check .
命令适用于使用 BuildKit 的 Docker CLI。确认不再出现 unknown instruction,并检查构建日志中演示文件的内容;之后还要使用实际软件的配置检查方式验证格式和路径。演示中的 cat 仅适用于无敏感信息的内容,不要照搬到会把凭据写入构建日志的配置上。
若已有 CI 固定前端版本,先在测试构建中确认兼容性,再修改正式流水线。保留变更前 Dockerfile 与配置文件;需要回退时恢复这两者及原前端设置。单纯恢复错误的多行引号写法不会恢复可用构建,回退目标应是此前能够成功解析的版本。


