RUN vs CMD vs ENTRYPOINT in Dockerfile

docker logo

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

简单的说:

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

Shell和Exec格式

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

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

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

RUN

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

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

安装多个包的例子:

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

注意: apt-get updateapt-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

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

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

CMD echo "Hello world"

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

Hello world

但当后面加上一个命令,比如 docker run -it [image] /bin/bashCMD 会被忽略掉,命令 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 格式

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

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

比如下面的 Dockerfile 片段:

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

CMD ["world"]

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

Hello world

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

Hello TonyDengq

Shell 格式

ENTRYPOINTShell 格式会忽略任何 CMDdocker run 提供的参数。

最佳实践

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

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

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

参考

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