cmd和entrypoint指令都是用来指定容器启动时运行的命令。
单从功能上看,这两个命令几乎是重复的。但是使用其中一个命令就可以实现绝大多数的用例。但是既然docker同时提供了它们,为了在使用中不至于混淆,我们在这里看看能不能理清楚。
exec模式和shell模式
cmd和entrypoint指令都支持exec模式和shell模式的写法。这两种模式的最主要用来指定容器中的不同进程为1号进程。我们在前面的文章中已经说明了,1号进程在linux系统中是非常重要的。
exec模式
使用exec模式时,容器中的任务进程就是容器中的1号进程,看下面的例子:
FROM ubuntuCMD [ "top" ]
把上面的代码保存到test1目录的Dockerfile中,然后进入test1目录构建镜像并启动一个容器:
$ docker build -t test1 .
$ docker run -idt --name testcon test1
然后查看容器中的进程ID:
$ docker exec testcon ps aux

从图中我们看到运行top命令的进程ID为1,exec模式是建议的使用模式,因为当运行任务的进程作为容器中的1号进程时,我们可以通过docker的stop命令优雅地结束容器。
exec模式的特点是不会通过shell执行相关命令,所以像¥HOME这样的环境变量是取不到的:
FROM ubuntu
CMD [ "echo", "$HOME" ]
把上面的代码保存到test目录的Dockerfile中,然后进入test1目录构建镜像并启动一个容器:
$ docker build --no-cache -t test1 .
$ docker run --rm test1

通过exec模式执行shell可以获得环境变量:
FROM ubuntu
CMD [ "sh", "-c", "echo $HOME" ]
把上面的代码保存到test1目录的Dockerfile中,然后进入test1目录结构构建镜像并启动一个容器:
$ docker build --no-cache -t test1 .
$ docker run --rm test1

这次正确取到了$HOME环境变量的值。
shell模式
使用shell模式时,docker会以/bin/sh -c “task command”的方式执行任务命令。也就是说容器中1号进程不是任务进程而是bash进程,看下面的例子:
FROM ubuntu
CMD top
把上面代码保存到test2目录的Dockerfile中,然后进入test2目录构建镜像并启动一个容器:
$ docker build -t test2 .
$ docker run -itd --name testcon2 test2
然后查看容器中的进程ID:
$ docker exec testcon2 ps aux

1号进程执行的命令居然是/bin/sh -c top, 而我们指定的top命令的进程ID为7。这是由docker内部决定的,目的是让我们执行的命令或者脚本可以取到环境变量。
CMD指令
cmd指令的目的是,为容器提供默认的执行命令。cmd指令有三种使用方式:
- CMD[“param1”, “param2”]
- CMD[“executable”, “param1”, “param2”]
- CMD command param1 param2
注意,命令行参数可以覆盖CMD指令的设置,但是只能是重写。却不能给CMD中的命令通过命令行传递参数。一般的镜像都会提供容器启动时的默认命令,但是有些场景中用户不想执行默认的命令。用户可以通过命令行参数的方式覆盖CMD指令提供的默认命令。比如通过下面的命令创建镜像:
FROM ubuntu
CMD [ "top" ]
在启动容器时我们通过命令行指定参数ps aux覆盖默认的top命令:
从上面的图可以看到,命令行上指定的ps aux命令覆盖了Dockerfile中CMD[“top”]。
ENTRYPOINT指令
ENTRYPOINT指令的目的也是为容器指定默认的执行任务,它也有两种使用方式,就是我们前面介绍的exec模式和shell模式:
- ENTRYPOINT[“executable”, “param1”, “param2”]
- ENTRYPOINT command param1 param2
其实ENTRYPOINT和CMD的基本用法是一致的,下面介绍一种特殊的用法:
FROM ubuntu
ENTRYPOINT [ "top", "-b" ]
运行下面的命令:
$ docker run --rm test1 -c

我们在命令行上添加的参数被追加到了top命令的参数列表中。
