Docker中CMD和Entrypoint的区别
Docker 中的 CMD 和 ENTRYPOINT 经常让人迷糊,尤其是同时使用时,更容易出现“命令不按预期执行”“容器秒退”等问题。理解它们的本质差异,是写好 Dockerfile 的关键。
核心理解:谁是“主角”,谁是“默认参数”
在 Docker 里,可以简单地这样理解:
ENTRYPOINT:定义“这个镜像本质上是干什么的”,也就是固定的主命令 / 主进程。CMD:为这个镜像提供默认参数,或者在没有ENTRYPOINT时,提供默认要执行的命令。
当二者同时存在时:
- Docker 实际运行的是:
ENTRYPOINT + CMD - 且
CMD会被当成是ENTRYPOINT的参数,而不是两个独立执行的命令。
所以:
1 | ENTRYPOINT ["top"] |
真实执行的是:
1 | top curl --help |
而不是先执行 top 再执行 curl --help,这也是许多初学者踩坑的地方。
三种经典写法与执行效果
Docker 支持两种语法风格:shell 形式 和 exec 形式。这里重点看更推荐、更直观的 exec 形式(JSON 数组写法)。
1. 只有 CMD
1 | FROM alpine:latest |
执行:
1 | docker run --rm my-image |
实际命令是:
1 | curl --help |
如果你在 docker run 时追加了命令:
1 | docker run --rm my-image curl https://example.com |
则会完全覆盖 CMD,真实运行的是:
1 | curl https://example.com |
要点:
- 没有
ENTRYPOINT时,CMD是“默认要执行的命令”,可被运行时参数轻松覆盖。
2. 只有 ENTRYPOINT
1 | FROM alpine:latest |
执行:
1 | docker run --rm my-image --help |
真实运行的是:
1 | curl --help |
再看一个例子:
1 | docker run --rm my-image https://example.com |
真实运行的是:
1 | curl https://example.com |
要点:
ENTRYPOINT定死了“主角”是curl,无论你在docker run后面写什么,都是给curl当参数。- 这种写法适合“就是要把这个镜像当作某个命令的专用工具”的场景,例如 CLI 工具封装。
3. ENTRYPOINT + CMD 组合
这是最容易踩坑但也最强大的用法。
1 | FROM alpine:latest |
不加任何额外参数时:
1 | docker run --rm my-image |
真实运行:
1 | curl --help |
如果在运行时追加参数:
1 | docker run --rm my-image https://example.com |
真实运行:
1 | curl https://example.com |
要点:
CMD给ENTRYPOINT提供默认参数,在不传参时生效。- 一旦运行时传入参数,就会覆盖 CMD,但不会覆盖 ENTRYPOINT。
真实踩坑案例:为什么这段 Dockerfile 跑不起来?
看下面这个例子(与前面提到的情况一致):
1 | FROM alpine:latest |
表面期望:
ENTRYPOINT跑top,保持容器持续运行。CMD跑curl --help,在启动时打印帮助。
实际发生的事:
- Docker 试图执行的命令是:
top curl --help curl和--help被作为参数传给了top。top不认识curl这样的参数,于是报错退出,容器直接结束。
也就是说:
- 你原本以为是“先跑 top,再跑 curl –help”;
- 实际上是“用 top 去执行 curl –help 这种不合法的参数组合”。
根本原因:CMD 永远不会作为一个“第二条命令”被执行,它只是给 ENTRYPOINT(或默认 shell)提供参数。
想顺序执行多个命令应该怎么写?
如果希望:
- 容器启动时执行一次
curl --help; - 然后再启动一个“持续运行”的程序(例如
top),让容器不退出;
有两种常见写法。
方案 A:用脚本做真正的入口(推荐)
entrypoint.sh:
1 |
|
Dockerfile:
1 | FROM alpine:latest |
特点:
- 脚本的最后一个前台进程(这里是
top)会成为容器的 PID 1。 - 可以在脚本里写完整的业务逻辑、异常处理、日志输出等。
方案 B:使用 sh -c 串联命令(方便测试)
如果只是做本地实验,图省事,可以直接在 CMD 里写一行 shell:
1 | FROM alpine:latest |
这里没有 ENTRYPOINT,容器启动时会执行:
1 | sh -c "curl --help && top" |
执行顺序:
- 先执行
curl --help,退出码为 0。 - 再执行
top,top在前台运行,容器保持存活。
缺点:
- 复杂逻辑都堆在一行 shell 字符串里,可读性差。
- 不利于后期维护,生产环境更推荐脚本方式。
常见使用场景与选择建议
1. 应用服务镜像(Web/后端服务)
典型 Dockerfile:
1 | FROM python:3.12-slim |
建议:
- 大多数 Web 服务只用
CMD就够了。 - 如非必要,不要使用
ENTRYPOINT,保持灵活(方便在调试时覆盖为/bin/bash)。
2. CLI / 工具型镜像
典型 Dockerfile:
1 | FROM alpine:latest |
特征:
- 这个镜像“就是一个 curl 工具箱”。
- 直接
docker run相当于执行curl --help。 - 传参时可以很自然:
1 | docker run --rm my-curl https://example.com |
3. 多进程 / 复杂启动逻辑
典型需求:
- 启动前做一些准备工作;
- 启动一个或多个长期服务;
- 监听信号,优雅退出。
推荐:
- 用脚本做
ENTRYPOINT,在里面管理所有流程。 - 或使用进程管理器(如
tini、supervisord),但这已经超出“简单 Dockerfile”的范畴。
总结:如何简单记住它们?
可以用一句话概括:
ENTRYPOINT决定“这个镜像是谁”,CMD决定“它默认怎么干活”。
- 只需要一个默认命令:用
CMD。 - 需要一个固定不变的主命令:用
ENTRYPOINT。 - 既要固定主命令,又要可配置默认参数:同时用
ENTRYPOINT + CMD,记住 CMD 是参数,不是另一条命令。 - 需要顺序执行多条命令:用脚本或
sh -c,不要指望多个 CMD/ENTRYPOINT 帮你“自动依次执行”。
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.





