RUN vs CMD vs ENTRYPOINT in Dockerfile

docker logo

RUN、CMD 和 ENTIRYPOINT这三个Dockerfile指令看起来都很类似,很容易搞混。我们来通过一些实践来详细讨论一下它们之间的差别。

简单的说:

  1. RUN执行命令并创建新的镜像层,RUN经常用来安装Docker image中需要的软件。
  2. CMD设置容器启动后默认执行的命令及其参数,但CMD能够被docker run后面跟着的命令行参数代替。
  3. ENTIRYPOINT配置容器启动时运行的命令。

Shell和Exec格式

我们可以用Shell和Exec两种方式指定RUN、CMD和ENTIRYPOINT要运行的命令,这两者在使用中有细微的区别。

Shell格式

<instruction> <command>

例如:

RUN apt-get install python3
CMD echo "Hello World"
ENTRYPOINT echo "Hello World"

当指令执行是,shell格式底层会调用/bin/sh -c <command>。

例如下面的Dockerfile片段:

ENV name Tony Deng
ENTRYPOINT echo "Hello, $name"

注意环境变量name已经被值Tony Deng替换了

Exec格式

<instruction> ["executable","param1","param2",...]

例如:

RUN ["apt-get","install","python3"]
CMD ["/bin/echo","Hello World"]
ENTRYPOINT ["/bin/echo","Hello World"]

当指令执行是,会直接调用<command>,不会被shell解析。

例如下面的Dockerfile片段:

ENV name Tony Deng
ENTRYPOINT ["/bin/echo","Hello, $name"]

运行容器将输出

Hello, $name

注意环境变量name没有被替换

如果希望使用环境变量,可以这样来修改

ENV name Tony Deng
ENTRYPOINT ["/bin/sh","-c","echo Hello, $name"]

这样,容器将会输出

Hello, Tony Deng

CMD和ENTRYPOINT推荐使用Exec格式,因为指令可读性更强,更容易理解,RUN则两种格式都可以。

RUN

RUN指令通常用于安装应用和软件包。

RUN在当前镜像的顶部执行命令,并创建新的镜像层。Dockerfile中常常包含多个RUN指令。

安装多个包的例子:

RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercuial \
subversion

注意: apt-get update 和 apt-get install最好放在一个RUN指令中执行,这样能够保证每次安装都是最新的包。如果apt-get install在单独的RUN执行,则会使用apt-get update创建新的镜像层,而这一层可能是很久以前缓存的。

CMD

CMD指令允许用户指定容器的默认执行的命令。

此命令会在容器启动且docker run没有指定其他命令时执行。

  1. 如果docker run指定了其他命令,CMD指定的默认命令将被忽略。
  2. 如果Dockerfile中有多个CMD指令,只有最后一个CMD生效。

CMD有三种格式:

  1. Exec格式: CMD ["executable","param1","param2",...]这是CMD推荐的格式。
  2. CMD ["param1","param2"]为ENTRYPOINT提供额外的参数,此时ENTRYPOINT必须使用Exec格式。
  3. Shell格式: CMD command param1 param2

Exec 和 Shell 格式前面已经介绍过了。
第二种格式CMD ["param1","param2"]要与Exec格式的ENTRYPOINT指令配合使用,其用途是为ENTRYPOINT设置默认的参数。我们将在后面讨论ENTRYPOINT时举例说明。

下面看看 CMD 是如何工作的。Dockerfile 片段如下:

CMD echo "Hello world"

运行容器 docker run -it [image] 将输出:

Hello world

但当后面加上一个命令,比如 docker run -it [image] /bin/bash,CMD 会被忽略掉,命令 bash 将被执行:

root@10a32dc7d3d3:/#

ENTRYPOINT

ENTRYPOINT指令可让容器以应用程序或者服务的形式运行。

ENTRYPOINT看上去与CMD很像,它们都可以指定要执行的命令及其参数。不同的地方在于 ENTRYPOINT不会被忽略,一定会被执行,即使运行docker run时指定了其他命令。

ENTRYPOINT有两种格式:

  1. Exec 格式:ENTRYPOINT ["executable", "param1", "param2"] 这是 ENTRYPOINT 的推荐格式。
  2. Shell 格式:ENTRYPOINT command param1 param2

在为 ENTRYPOINT 选择格式时必须小心,因为这两种格式的效果差别很大。

Exec 格式

ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。

ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。

比如下面的 Dockerfile 片段:

ENTRYPOINT ["/bin/echo", "Hello"]  

CMD ["world"]

当容器通过 docker run -it [image] 启动时,输出为:

Hello world

而如果通过 docker run -it [image] TonyDeng 启动,则输出为:

Hello TonyDengq

Shell 格式

ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。

最佳实践

使用 RUN 指令安装应用和软件包,构建镜像。

如果 Docker 镜像的用途是运行应用程序或服务,比如运行一个 MySQL,应该优先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可为 ENTRYPOINT 提供额外的默认参数,同时可利用 docker run 命令行替换默认参数。

如果想为容器设置默认的启动命令,可使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。

参考

Dockerfile: ENTRYPOINT vs CMD
Abhishek Tomar在Slideshare关于Docker的分享