DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Docker 派生镜像新增 ENTRYPOINT 后,入口脚本收不到基础镜像的 CMD

派生 Dockerfile 设置新的 ENTRYPOINT 时,基础镜像的 CMD 会被清空。对照镜像与容器启动字段,在当前镜像中声明默认命令,并保留原入口的初始化要求。

基础镜像直接运行时能启动服务,派生镜像加了一个 ENTRYPOINT 脚本后,脚本里的 "$@" 却没有任何参数。文件都已复制成功,构建也没有报错,原来的默认启动命令却没有传进来。

在当前 Dockerfile 规则下,如果 CMD 来自基础镜像,在派生镜像中设置 ENTRYPOINT 会把继承的 CMD 清空。要让新的入口收到默认参数,必须在当前镜像中明确声明 CMD。这不表示 ENTRYPOINT 和 CMD 不能一起使用,而是新的入口不能自动沿用基础镜像的默认命令。

以下针对 Linux 镜像,以及使用 JSON 数组形式调用包装脚本的入口。先确认确实发生了这种构建继承关系,再改配置;入口脚本丢弃参数、运行时覆盖命令也会产生相似症状。

对照基础镜像与派生镜像的启动字段

把两个镜像名替换成实际构建所用的版本:

docker image inspect --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' base-app:release
docker image inspect --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' derived-app:check

如果基础镜像的 Cmd 有内容,而派生镜像的 Entrypoint 已变成自有脚本、Cmd 却是 null 或空数组,检查派生 Dockerfile 是否只写了新的 ENTRYPOINT。

不要只检查基础镜像当前标签。基础镜像标签可能已移动,应对照构建记录中实际使用的版本或摘要。诊断也不需要输出整个镜像环境变量,启动参数中若包含秘密值,应先遮蔽再保存记录。

若派生镜像的 Cmd 本来就正确,再检查创建出的容器:

docker inspect --type container --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' app-container

app-container 是目标容器的占位名称。运行时传入的命令会覆盖镜像默认 CMD;如果创建出的容器与镜像不同,要沿着部署参数检查,不能继续把问题归到构建继承。

在当前 Dockerfile 中明确给出默认命令

先确认新的入口脚本接受什么参数。一个只做准备工作、最后执行 exec "$@" 的包装脚本,需要收到完整程序名及参数;如果入口已经固定调用应用,CMD 通常只负责该应用的默认参数。这两种结构不能机械互换。

下面是一组独立演示文件,用于看清继承关系。不要把这个简单包装脚本直接替换到第三方业务镜像中。

基础镜像的文件 Dockerfile.base:

FROM busybox:1.36
CMD ["/bin/echo", "base-command"]

包装脚本 start-demo.sh:

#!/bin/sh
set -eu
if [ "$#" -eq 0 ]; then
    printf '%s\n' 'missing startup command' >&2
    exit 64
fi
exec "$@"

派生镜像的文件 Dockerfile:

FROM cmd-base:local
COPY start-demo.sh /usr/local/bin/start-demo
RUN chmod 755 /usr/local/bin/start-demo
ENTRYPOINT ["/usr/local/bin/start-demo"]
CMD ["/bin/echo", "base-command"]

最后一行重新声明了当前镜像的默认命令。去掉它,新入口将没有继承来的默认参数;脚本会按这里定义的规则输出 missing startup command 并返回 64。这是演示脚本自定义的错误,不是 Docker 固定返回的报错或退出码。

exec "$@" 保留每个参数的边界,并用目标程序替换包装 shell。不要改成 exec $@ 或用 eval 拼接命令,否则参数中的空格、通配符等会引入另一类问题。

用新标签验证默认值和显式覆盖

将以上三个文件放在同一个本机测试目录。下面的命令只演示构建与运行这两个测试镜像,不挂载业务数据;基础镜像本机不存在时需要拉取:

docker build -f Dockerfile.base -t cmd-base:local .
docker build -t cmd-derived:check .
docker image inspect --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' cmd-derived:check
docker run --rm cmd-derived:check
docker run --rm cmd-derived:check /bin/echo override-command

按这组文件定义,默认运行应打印 base-command,显式传入命令时应打印 override-command。这分别检查默认 CMD 已恢复,以及运行时参数仍能通过包装脚本传给目标程序。若第一条构建失败,应先修复构建问题,不要继续把旧标签的运行结果当作本次镜像的验证。

真实服务还要验证入口原本承担的初始化、权限调整和信号处理。基础镜像可能有自己的 ENTRYPOINT;你在派生镜像中重新设置它后,原入口的逻辑不会自动继续执行。补回 CMD 只解决默认命令缺失,不能补回被绕过的初始化流程。

如果并不需要新的包装行为,保留基础镜像原来的入口可能更合适。需要包装时,应先弄清原入口的调用约定,设计明确的调用链,而不是只复制一个 CMD 就认定整个镜像行为一致。

应用修正时保留原镜像

以新版本标签构建修正后的业务镜像,在隔离环境确认默认启动与显式覆盖行为后,再按现有部署流程替换容器。重启旧容器不会让它采用新镜像的启动字段。

保留上一份镜像版本和部署定义;涉及持久数据的服务仍需现有备份。若新入口还有其他问题,恢复旧镜像与对应启动配置。镜像回退不会撤销程序已经写入的数据,也不需要为这类启动配置问题删除卷或清空构建缓存。