跳到主要内容
DevOps2026-09-085 分钟阅读

executable file not found:Docker 容器启动即退出的 4 种原因(含 exec format error)

现象:容器起来一瞬就退出

# 容器启动时
standard_init_linux.go: exec: "./start.sh": stat ./start.sh: no such file or directory

# 或 docker exec 时
OCI runtime exec failed: exec: "sh": executable file not found in $PATH: unknown

# 或另一种形态
exec ./start.sh: no such file or directory

一行结论:容器里找不到你让它执行的那个文件/命令。最大的陷阱在于:报 no such file or directory 时,文件往往就在那里——真正缺失的是脚本的解释器(shebang 指向的程序不存在)。这是 Docker 报错里最误导人的一条。

先别急着改 Dockerfile。把错误抄全(含 standard_init_linux.go / OCI runtime / exec format error 原文),再按下面四类对照——多数工单在第一类(CRLF)或第四类(架构)就结束。

四种原因,按概率排序

1. Windows 的 CRLF 行尾毁掉了 shebang(最阴险)

在 Windows 上编辑的 shell 脚本行尾是 \r\n,第一行实际变成了:

#!/bin/sh\r

Linux 找的是 sh\r 这个程序——不存在,于是报 no such file or directory,而你 ls 看到脚本明明在。验证与修复:

docker run --rm --entrypoint sh myapp -c "head -1 /app/start.sh | od -c | head -2"
# 看到 \r  \n 就是它了

# 修复:仓库加 .gitattributes
*.sh text eol=lf

编辑器右下角把 CRLF 切换成 LF 重存一遍也行。CI 里若有 Windows runner 检出脚本,记得统一 core.autocrlf 或强制 eol=lf,否则本地修好、流水线再弄坏。

2. ENTRYPOINT 的 exec 形式里用了环境变量拼接

# ❌ exec 形式不会经过 shell,$JAVA_OPTS 不展开,字面量去找
ENTRYPOINT ["java", "$JAVA_OPTS", "-jar", "app.jar"]

# ✅ 要展开变量就用 shell 形式(注意会丢信号传递)
ENTRYPOINT java ${JAVA_OPTS} -jar app.jar

exec 形式 ["a","b"] 不启动 shell,$VAR、~、通配符都是字面量;shell 形式 a b 经过 /bin/sh -c,变量会展开。分不清这两种形式是这个错误的高发源头。同理,ENTRYPOINT ["npm", "start"] 找不到 npm,多半是基础镜像里 npm 不在 PATH 或装在别的用户目录下。

折中写法:用小型 entrypoint 脚本接收参数,脚本末尾 exec 真正进程,既保留展开能力又保住信号。可用 Dockerfile 生成器 先搭骨架,再手工补 entrypoint。

3. 路径问题:相对路径与 WORKDIR

WORKDIR /app
COPY start.sh .          # 文件在 /app/start.sh
ENTRYPOINT ["./start.sh"]  # ✅ 相对 WORKDIR
ENTRYPOINT ["/start.sh"]   # ❌ 根目录下没有

docker exec 进容器手动跑一遍最快:

docker run --rm --entrypoint sh myapp -c "ls -la /app && which npm"

多阶段构建时,上一阶段 COPY --from=builder 漏了可执行位或路径写错,也会表现为「文件在构建上下文里明明有」。进最终镜像用 ls -la 看权限位,chmod +x 要写进 Dockerfile 而不是只在本机改一次。

4. 架构不匹配:exec format error

exec /app/start.sh: exec format error

M 系列 Mac 构建的 arm64 镜像跑在 x86 服务器上(或反之)。这不是文件找不到,是二进制格式不对。构建时锁平台:

docker buildx build --platform linux/amd64 -t myapp --push .

docker inspect --format '{{.Architecture}}' myapp 确认镜像架构。

调试三板斧

# 1. 看镜像实际配置的入口
docker inspect myapp --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}'

# 2. 绕过入口直接进容器看现场
docker run --rm --entrypoint sh myapp
# 进去后:ls 确认文件、head -1 xxx.sh | od -c 查 CRLF、which xxx 查 PATH

# 3. 精简 Dockerfile 里逐步执行,哪一步炸的一目了然
docker run --rm myapp ls /app

精简基础镜像(scratch / distroless)里往往没有 sh——此时 --entrypoint sh 本身就会报 not found。换带 shell 的调试标签镜像,或用 docker create + docker cp 把二进制拷出来用宿主机 file 检查。

ENTRYPOINT 与 CMD:shell 是怎么丢的

shell 形式(CMD npm start)跑 /bin/sh -c——PATH 查找正常但信号到不了 npm(PID 1 是 sh);exec 形式(CMD ["npm","start"])完全跳过 shell——绝对路径没问题,依赖 PATH 或 shell 展开的就坏。Windows 路径陷阱:在 Windows 上编辑的 Dockerfile 带尾随 \r——容器找名为 npm\r 的二进制。稳妥模式:exec 形式 + 绝对路径,.gitattributes 给 Dockerfile 标 LF。

把历史 docker run 迁到 Compose 时,entrypoint/command 字段同样要分清数组与字符串两种写法;可用 docker run 转 compose 对照,再人工确认是否误用了 shell 展开。

架构静默失败

错架构的二进制不总报 exec format error——加载器缺失时有时表现为 not found。容器内一条 file /app/binary 说真话:x86-64 还是 aarch64。交叉构建从 CI 的 arm 原生跑默认宿主、从 Mac 拷贝二进制进 linux/amd64 镜像,都是这类案的常客。Go/Rust 静态二进制记得在构建参数里锁 GOARCH / --target,不要「本机编完 COPY 进镜像」就当跨平台完成。

entrypoint 脚本:exec 或孤儿

自定义 entrypoint.sh 最后一行是 node server.js(没有 exec)时,sh 成了 PID 1——信号断路,docker stop 超时后升级 SIGKILL,优雅关闭没了。最后一行必须是 exec "$@"。它不直接造成 not found,但调试弯路通常两个症状一起过。

和「镜像里根本没有 shell」的差集

报错原文若是 exec: "bash": executable file not found,先看基础镜像:Alpine 默认只有 sh(BusyBox),Debian slim 才有 bash。ENTRYPOINT 写 ["/bin/bash", …] 对 Alpine 必炸。改写成 ["/bin/sh", …],或显式安装 bash——不要假设「Linux 都有 bash」。

排查清单

  1. 文件真的在吗?--entrypoint sh 进容器 ls(精简镜像没有 sh 时换调试镜像)
  2. 在但报 not found?od -c 查 shebang 的 CRLF(Windows 编辑的脚本九成是它)
  3. ENTRYPOINT 是 exec 形式还想用 $VAR?改 shell 形式或用 entrypoint 脚本
  4. 路径相对的是 WORKDIR,不是你以为的目录;检查可执行位
  5. exec format error → 架构不匹配,buildx --platform 重构建
  6. 找不到 bash/npm → 基础镜像 PATH 与包是否存在,不要抄别的发行版的绝对路径

排查注记

四类原因按概率排序,但 CRLF 单独标「最阴险」——它利用的是报错本身的误导性:报 no such file or directory 时文件往往就在那里,缺的是 shebang 指向的解释器,ls 确认文件存在只会让人更晕。文中明说多数工单在第一类(CRLF)或第四类(架构)就结束,所以验证命令选 od -c 看 shebang 的字节而不是 cat 看内容,就是为了绕开「肉眼看脚本明明在」这个陷阱。


本文由 ToolVault 工具匣 提供。相关工具:Dockerfile 生成器、docker run 转 compose、Linux 命令速查。相关阅读:Docker daemon 连不上排查、容器名冲突排查、磁盘不足排查。访问 首页 查看更多开发者工具。