不喜欢广告? 无广告 今天

Docker ENTRYPOINT 与 CMD ——你的容器欺骗了你

更新于

你在 Dockerfile 中将 ENTRYPOINT 和 CMD 组合在一起,容器启动了错误的内容,现在你来到了这里。这里有一个完整的分析——所有可能的组合、shell 与 exec 形式陷阱,以及真正有效的模式。

Docker ENTRYPOINT 与 CMD —— 你的容器在欺骗你 1
广告 移除?

错误发生在凌晨2点。你启动容器后,本应看到API服务器,却只看到一个shell提示符,或者完全没有任何内容,或者你的进程被一个“幽灵”包裹运行。 shSIGTERM 像糖果一样——优雅关闭需要10秒的Docker等待,之后才会放弃并发送 SIGKILL.

罪魁祸首,几乎每次都是:你混淆了 ENTRYPOINTCMD。或者以Docker默许接受的方式组合——只是不符合你的预期。

CMD:你可以替换的默认值

CMD 定义了容器启动时运行的命令——但它只是一个建议,而不是强制规则。在镜像名称后传递任何内容,都会被完全替换:

FROM ubuntu
CMD ["echo", "hello from CMD"]
$ docker run myimage
hello from CMD

$ docker run myimage echo goodbye
goodbye

echo goodbye 没有附加——它直接替换了。你的整个 CMD 消失了。这是设计如此: CMD 是默认行为,而不是强制行为。任何运行时参数都会覆盖它。

ENTRYPOINT:始终运行的部分

ENTRYPOINT 定义了无论何种情况都会运行的可执行文件。运行时参数不会替换它,而是传递给它:

FROM ubuntu
ENTRYPOINT ["echo"]
CMD ["hello"]
$ docker run myimage
hello

$ docker run myimage goodbye
goodbye

$ docker run --entrypoint cat myimage /etc/hostname
mycontainer-abc123

当两者都设置时, ENTRYPOINT 是可执行文件, CMD 成为其默认参数。你可以自由地覆盖 CMD ,但 ENTRYPOINT 只有在你显式传递 --entrypoint.

时才会被覆盖。

所有ENTRYPOINT与CMD组合的详细说明

ENTRYPOINTCMD实际运行的内容
始终设置用于身份验证的Cookie始终设置用于身份验证的Cookie错误——容器需要从某个地方获取命令
始终设置用于身份验证的Cookie["cmd", "arg"] exec形式cmd arg
始终设置用于身份验证的Cookiecmd arg shell形式/bin/sh -c "cmd arg"
["entry"] exec形式始终设置用于身份验证的Cookieentry
["entry"] exec形式["arg1", "arg2"] exec形式entry arg1 arg2
["entry"] exec形式cmd arg shell形式entry /bin/sh -c "cmd arg" — 几乎肯定是错误的
entry shell形式["arg1"] exec形式/bin/sh -c "entry"CMD被静默忽略
entry shell形式cmd arg shell形式/bin/sh -c "entry"CMD被静默忽略

标记为“CMD被静默忽略”的两行内容,导致了大量Docker调试会话。shell形式 ENTRYPOINT 不会与 CMD 合并——它会完全忽略它。Docker不会警告你这一点。

shell形式与exec形式:信号处理陷阱

两种指令都接受两种形式,而选择的重要性远超大多数Dockerfile教程所承认的。

exec形式 (数组语法):

ENTRYPOINT ["nginx", "-g", "daemon off;"]

你的二进制文件直接运行。它成为PID 1。当Docker发送 SIGTERM 来停止容器时,你的进程会收到该信号。优雅关闭得以实现。日志被刷新。连接被干净关闭。

Shell 形式 (普通字符串):

ENTRYPOINT nginx -g "daemon off;"

Docker会以 /bin/sh -c "nginx -g daemon off;"的形式运行它。shell成为PID 1。当 SIGTERM 到达时, sh 接收到它——而 sh 不会将信号转发给子进程。你的容器会挂起10秒,接收到 SIGKILL,然后在没有清理的情况下死亡。每次都是如此。

始终使用exec形式。对于两者都使用。 ENTRYPOINTCMD.

三种真正有效的模式

模式1:固定的可执行文件,可替换的默认参数

大多数生产容器的最佳模式。可执行文件是固定的,参数可以在运行时切换:

ENTRYPOINT ["/app/server"]
CMD ["--port", "8080", "--env", "production"]
# Use defaults
docker run myimage

# Override at deploy time
docker run myimage --port 9090 --env staging

模式2:使用exec的包装脚本

当你需要在主进程之前执行初始化逻辑(如迁移、密钥注入、信号捕获)时,使用包装脚本。关键行是 exec "$@" 在结尾处——它用你的CMD替换shell进程,使你的二进制文件成为PID 1:

#!/bin/sh
set -e

echo "Running migrations..."
/app/migrate

# Hand off to CMD — exec replaces shell, so /app/server becomes PID 1
exec "$@"
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/app/server", "--port", "8080"]

如果你跳过 exec "$@",shell将保持为PID 1,你又回到了信号处理问题。

模式3:仅使用CMD,不使用ENTRYPOINT

适用于开发镜像或需要在相同环境中运行任意命令的工具容器:

FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

在生产环境中,模式1或模式2更安全——你不想让一个配置错误的部署脚本意外运行 docker run myimage bash 并用shell会话替换你的服务器。

docker exec 与此有何关系

没有。 docker exec 在已运行的容器中执行一个命令。它完全绕过 ENTRYPOINT 。当你运行 ENTRYPOINT 时,你无需考虑 docker exec mycontainer bash ——你是在与运行中的容器环境交互,而不是启动配置。

混淆通常来自那些使用 docker exec 调试并确认一切正常,然后发现 docker run 行为不同的用户。它们是完全独立的代码路径。

发货前检查清单

  • 两者相同 ENTRYPOINTCMD 使用exec形式(数组语法,而非普通字符串)
  • 如果你有包装脚本,它必须以 exec "$@"
  • 结尾 docker run myimagedocker run myimage --your-flag 你已测试
  • docker stop mycontainer 确保两个路径都能在2秒内完成(不是10秒——如果耗时10秒,说明你存在信号处理问题)

如果你想在Dockerfile接近注册表之前获得自动反馈, IO Tools’ Dockerfile Linter 会检测到shell形式的使用、entrypoint脚本中缺失的 exec 以及其他在运行时导致静默错误的模式。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?