bigywater.tech
Docker · 10

ENTRYPOINT 与 CMD:两者组合后的最终命令

ENTRYPOINT 和 CMD 的搭配规则用文字很难记。这篇不背规则,直接把九种写法都跑出来,看容器最终执行的 Path 和 Args,再看 shell 形式对 PID 1 的影响。

只换 CMD
docker run 镜像名后面的参数替换 CMD,ENTRYPOINT 保留(Args=["x"])
收到 0 个参数
shell 形式的 ENTRYPOINT 加 CMD ["a","b"] 时,入口脚本拿到的参数个数
PID 1 = sh
ENTRYPOINT 是复合 shell 命令(sleep 300 && echo done)时的 PID 1

九种组合

实测 · 本机执行输出
  [1] exec 形式 ENTRYPOINT + exec 形式 CMD,不加运行参数
      Dockerfile:ENTRYPOINT ["/e.sh"] ; CMD ["a", "b"]  运行参数:(无)
      镜像里存的 Entrypoint / Cmd = ["/e.sh"] ["a","b"]
      容器实际执行:Path=/e.sh Args=["a","b"]
      输出:入口脚本收到 2 个参数:a b
  [2] 同上,docker run 后面加参数 x(替换 CMD)
      Dockerfile:ENTRYPOINT ["/e.sh"] ; CMD ["a", "b"]  运行参数:x
      镜像里存的 Entrypoint / Cmd = ["/e.sh"] ["a","b"]
      容器实际执行:Path=/e.sh Args=["x"]
      输出:入口脚本收到 1 个参数:x
  [3] 同上,用 --entrypoint 覆盖入口
      Dockerfile:ENTRYPOINT ["/e.sh"] ; CMD ["a", "b"]  运行参数:--entrypoint echo IMG new
      镜像里存的 Entrypoint / Cmd = ["/e.sh"] ["a","b"]
      容器实际执行:Path=echo Args=["new"]
      输出:new
  [4] shell 形式 ENTRYPOINT + exec 形式 CMD(CMD 被忽略)
      Dockerfile:ENTRYPOINT /e.sh ; CMD ["a", "b"]  运行参数:(无)
      镜像里存的 Entrypoint / Cmd = ["/bin/sh","-c","/e.sh"] ["a","b"]
      容器实际执行:Path=/bin/sh Args=["-c","/e.sh","a","b"]
      输出:入口脚本收到 0 个参数:
  [5] exec 形式 ENTRYPOINT + shell 形式 CMD
      Dockerfile:ENTRYPOINT ["/e.sh"] ; CMD echo hello  运行参数:(无)
      镜像里存的 Entrypoint / Cmd = ["/e.sh"] ["/bin/sh","-c","echo hello"]
      容器实际执行:Path=/e.sh Args=["/bin/sh","-c","echo hello"]
      输出:入口脚本收到 3 个参数:/bin/sh -c echo hello
  [6] 只有 exec 形式 CMD
      Dockerfile:CMD ["echo", "from cmd"]  运行参数:(无)
      镜像里存的 Entrypoint / Cmd = null ["echo","from cmd"]
      容器实际执行:Path=echo Args=["from cmd"]
      输出:from cmd
  [7] 同上,docker run 后面给了新命令(整个替换)
      Dockerfile:CMD ["echo", "from cmd"]  运行参数:ls /etc/alpine-release
      镜像里存的 Entrypoint / Cmd = null ["echo","from cmd"]
      容器实际执行:Path=ls Args=["/etc/alpine-release"]
      输出:/etc/alpine-release
  [8] 只有 shell 形式 CMD
      Dockerfile:CMD echo from shell cmd  运行参数:(无)
      镜像里存的 Entrypoint / Cmd = null ["/bin/sh","-c","echo from shell cmd"]
      容器实际执行:Path=/bin/sh Args=["-c","echo from shell cmd"]
      输出:from shell cmd
  [9] ENTRYPOINT 自带参数 + CMD,docker run 再给 over
      Dockerfile:ENTRYPOINT ["/e.sh", "fixed"] ; CMD ["default"]  运行参数:over
      镜像里存的 Entrypoint / Cmd = ["/e.sh","fixed"] ["default"]
      容器实际执行:Path=/e.sh Args=["fixed","over"]
      输出:入口脚本收到 2 个参数:fixed over

exec 形式的 ENTRYPOINT 会把 CMD(或 docker run 后面的参数)追加成它的参数;docker run 给了参数就整体替换 CMD;--entrypoint 直接覆盖入口。只有 CMD 时,run 后面的命令整个替换它。

shell 形式的坑与 PID 1

实测 · 本机执行输出
  [ENTRYPOINT ["sleep", "300"]] ps 看到:1 sleep 300 | 7 ps -o pid,args
  [ENTRYPOINT sleep 300] ps 看到:1 sleep 300 | 7 ps -o pid,args
  [ENTRYPOINT sleep 300 && echo done] ps 看到:1 /bin/sh -c sleep 300 && echo done | 7 sleep 300 | 8 ps -o pid,args

ENTRYPOINT /e.sh 被存成 ["/bin/sh","-c","/e.sh"],CMD ["a","b"] 只是 sh -c 的位置参数,脚本收到 0 个参数。CMD 用 shell 形式、ENTRYPOINT 用 exec 形式时,入口收到的是 /bin/sh -c "echo hello" 三个参数,通常不是你想要的。PID 1:exec 形式时就是你的程序;shell 形式 + 单条命令,这个 busybox sh 把它直接 exec 了,PID 1 仍是程序(别的 shell 不一定);复合命令(&&)时 PID 1 是 /bin/sh,你的程序是子进程,信号处理就不同了——后面讲 PID 1 与信号的文章会继续验证。

分步演示

ENTRYPOINT 与 CMD
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。每种组合是一个单独构建的镜像(alpine:3.20 + 一个打印参数的脚本),用 docker create 之后 docker inspect 取 .Path / .Args,并 docker start -a 看输出;PID 1 通过 docker exec ps 查看;实验后已删除所有镜像和容器
    • 没有验证:bash / dash 等其它 shell 的 exec 优化、docker compose 的 entrypoint / command 覆盖、ENTRYPOINT 为空数组的写法、CMD 在 ENTRYPOINT 之前声明的顺序影响、SHELL 指令改默认 shell 的效果
    京ICP备2026067502号-1京公网安备11010502063487号