在 Dockerfile 中,RUN、CMD 和 ENTRYPOINT 三个指令有什么区别和联系?
在 Dockerfile 中,RUN、CMD 和 ENTRYPOINT 是最容易混淆的三个指令。它们都用于执行命令,但执行的时机、作用以及使用场景有着本质的区别。
简单来说:
RUN:在镜像构建(Build)阶段执行,用于安装软件、配置环境,结果会保存到镜像中。ENTRYPOINT:在容器启动(Run)时执行,用于设置容器的默认入口程序,使其像一个可执行文件一样运行。CMD:在容器启动(Run)时执行,用于设置容器的默认启动命令和参数。它可以被docker run命令行中的参数覆盖。
一、 详细对比
| 特性 | RUN |
ENTRYPOINT |
CMD |
|---|---|---|---|
| 执行时机 | 镜像构建时 (docker build) |
容器启动时 (docker run) |
容器启动时 (docker run) |
| 主要用途 | 安装软件包、创建目录、写入文件等。 | 指定容器启动时必须运行的主程序。 | 指定容器启动时的默认命令或传给 ENTRYPOINT 的默认参数。 |
| 是否会被覆盖 | 不适用(构建后即成为镜像的一部分)。 | 很难被覆盖。必须在 docker run 中显式使用 --entrypoint 才能覆盖。 |
很容易被覆盖。docker run <image> <command> 中的 <command> 会直接覆盖 CMD。 |
| 在 Dockerfile 中出现的次数 | 可以出现多次,每次创建一个新的镜像层。 | 只能有一条生效。如果写了多条,只有最后一条生效。 | 只能有一条生效。如果写了多条,只有最后一条生效。 |
二、 深入理解:CMD 与 ENTRYPOINT 的合作关系
CMD 和 ENTRYPOINT 经常配合使用。它们都有两种书写格式:
- Shell 格式(不推荐):
CMD node app.js(会在后台启动一个/bin/sh -c来运行命令,无法接收系统信号)。 - Exec 格式(推荐):
CMD ["node", "app.js"](直接运行,解析为 JSON 数组)。
当它们配合使用时,ENTRYPOINT 充当“程序”,CMD 充当“默认参数”。
示例 1:经典的“工具类”容器
假设我们要制作一个执行 ping 命令的镜像。
plaintext
FROM alpine
ENTRYPOINT ["ping"]
CMD ["localhost"]
场景 A:直接启动容器
bashdocker run my-ping实际执行的命令是:
ping localhost(CMD的值作为参数传给了ENTRYPOINT)。场景 B:启动时传入新参数
bashdocker run my-ping google.com实际执行的命令是:
ping google.com。
解析:命令行中的google.com覆盖了CMD中的"localhost",但ENTRYPOINT保持不变。
三、 总结与最佳实践
使用
RUN来构建镜像:
所有关于安装软件(如apt-get install、npm install)、复制文件、授权等操作,都用RUN。使用
ENTRYPOINT设定容器的本质属性:
如果你的容器是一个“单一功能的工具”(如 Redis 容器、MySQL 容器、Ping 工具),应该使用ENTRYPOINT确保这个容器启动时一定会运行该服务。- 推荐格式:
ENTRYPOINT ["executable", "param1"]
- 推荐格式:
使用
CMD提供默认行为或参数:- 如果容器是一个 Web 服务(如 Node.js、Python 运行环境),可以使用
CMD ["npm", "start"],这样用户在启动时如果想运行交互式 Shell(如docker run -it my-app sh),可以直接覆盖它。 - 如果配合
ENTRYPOINT使用,CMD应该只包含参数:CMD ["--help"]。
- 如果容器是一个 Web 服务(如 Node.js、Python 运行环境),可以使用
右滑查看面试常问