dockerfile默认使用/bin/sh -c,不支持bash特性(如[[ ]]、source等),易出错;应通过shell ["/bin/bash", "-c"]切换,并在alpine等镜像中先安装bash;进阶可加set -euxo pipefail提升健壮性。

Dockerfile 默认使用 /bin/sh -c 作为命令解析器,这意味着所有 RUN、CMD 和 ENTRYPOINT(shell 形式)指令都由 POSIX 兼容的轻量 shell 执行。它不支持数组展开、某些 bash 特性(如 [[ ]]、source、set -eux 等),容易在复杂脚本中出错或静默失败。
为什么要改默认 SHELL
很多构建逻辑依赖 bash 特性:比如条件判断、函数定义、错误立即退出(set -e)、调试输出(set -x)、变量扩展增强等。用 /bin/sh 运行含 bash 语法的脚本会直接报错,例如:
-
RUN [[ -f /app/config.yml ]] && echo "OK"→/bin/sh: 1: [[: not found -
RUN source /etc/profile.d/env.sh && npm install→/bin/sh: 1: source: not found
用 SHELL 指令切换到 bash
只需在 Dockerfile 中靠前位置(通常在 FROM 后、首个 RUN 前)添加一行:
SHELL ["/bin/bash", "-c"]
此后所有 RUN、CMD(shell 形式)、ENTRYPOINT(shell 形式)都会通过 /bin/bash -c 解析执行。注意格式必须是 JSON 数组写法,不能写成 SHELL /bin/bash -c。
如果基础镜像没预装 bash(如 alpine),需先 RUN apk add --no-cache bash,再设 SHELL;否则构建会失败。
进阶:带调试和错误控制的 SHELL
可以组合 bash 参数提升健壮性,例如:
SHELL ["bash", "-c", "set -euxo pipefail; $1"]
说明:
-
set -e:任一命令非零退出,立即终止整个 RUN 步骤 -
set -u:引用未定义变量时报错,避免静默空值 -
set -x:打印每条实际执行的命令(含变量展开后),便于调试 -
set -o pipefail:管道中任意命令失败即整体失败(默认只看最后一个) -
$1是占位符,Docker 会把 RUN 后面的整条字符串传给它
这样写后,RUN echo "hello" && false && echo "world" 会在 false 处中断,不会继续执行下一条。
注意事项与常见陷阱
SHELL 只影响后续的 RUN、CMD、ENTRYPOINT 指令,不影响 COPY、ADD、ENV 等其他指令。它也不会改变容器运行时的默认 shell —— 容器启动后仍按 CMD 或用户指定命令执行,与构建期 SHELL 无关。
另外,CMD ["python", "app.py"](exec 形式)完全绕过 SHELL,不受 SHELL 指令影响;只有 CMD python app.py(shell 形式)才走当前 SHELL。
多个 SHELL 指令可叠加覆盖,后面生效的为准。适合分阶段切换环境,比如前期用 bash 构建,后期切回 sh 减小镜像体积。











