Docker 中 RUN、CMD 和 ENTRYPOINT 的区别算是一个比较难理解的知识点,总结一下
作用
RUN
执行命令并创建新的 image layer
CMD
- 容器启动后默认执行的命令
- 如果使用
docker run
命令启动容器时指定了其他命令,CMD 指令将被忽略
ENTRYPOINT
- 配置容器启动时运行的命令
- 不会被忽略,一定会执行
命令的格式
RUN、CMD 和 ENTRYPOINT 都支持两种格式的命令:Shell 和 Exec
Shell 格式
1 | <instruction> <command> |
比如
1 | RUN apt-get install python3 |
Shell 格式的底层实现是调用 /bin/sh -c [command]
来实现的
Exec 格式:
1 | <instruction> ["executable", "param1", "param2", ...] |
例如
1 | RUN ["apt-get", "install", "python3"] |
Exec 格式底层实现是把命令用空格分隔拼接起来,得到一个新的命令 [command]
,然后直接调用 [command]
当使用环境变量时
比较一下 CMD echo $PATH
和 CMD ["echo", "$PATH"]
的区别
执行 CMD echo $PATH
时,输出
1 | /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin |
使用 docker ps -l --no-trunc
观察 COMMAND
部分的内容,可以看到 CMD echo $PATH
变成了 /bin/sh -c 'echo $PATH'
1 | [node1] (local) root@192.168.0.13 ~/env |
执行 CMD ["echo", "$PATH"]
时,输出
1 | $PATH |
使用 docker ps -l --no-trunc
观察 COMMAND
部分的内容,可以看到 CMD ["echo", "$PATH"]
还是保持原样
1 | $ docker ps -l --no-trunc |
所以得出结论:
- Shell 格式的命令,会被包装为
sh -c
的参数的形式进行执行 - Exec 格式的命令则是原样执行。因此如果需要使用环境变量的话,应该写成这样:
CMD ["/bin/bash", "-c", "echo $PATH"]
建议
- RUN 指令使用 Shell 格式的命令即可
- CMD 和 ENTRYPOINT 指令推荐使用 Exec 格式的命令
ENTRYPOINT 和 CMD 组合使用
记住几点:
- CMD 设置容器启动时执行的命令,可以被
docker run
覆盖 - ENTRYPOINT 也是设置容器启动时执行的命令,无法被
docker run
覆盖,一定会执行
所以,可以将 ENTRYPOINT 和 CMD 组合使用:
ENTRYPOINT 指定默认的运行命令,CMD指定默认的运行参数
比如这个 Dockerfile
1 | FROM busybox |
构建镜像,然后执行
1 | docker build -t ikutarian/ping . |
输出
1 | PING localhost (127.0.0.1): 56 data bytes |
使用 docker ps -a --no-trunc
,查看一下 COMMAND
部分的内容
1 | $ docker ps -l --no-trunc |
可以看到容器启动时,执行的命令是 /bin/ping -c 3 localhost
上面的命令是用 ENTRYPOINT ["/bin/ping", "-c", "3"]
和 CMD ["localhost"]
组成而成的。CMD 指令的内容可以被 docker run
覆盖,如果希望容器启动之后 ping 的不是 localhost 而是其他的,可以这么做
1 | docker run ikutarian/ping www.google.com |
现在的输出结果是
1 | PING www.google.com (172.217.7.196): 56 data bytes |
用 docker ps -l --no-trunc
查看一下 COMMAND
的信息
1 | $ docker ps -l --no-trunc |
注意点
- Shell 格式的底层实现是调用
/bin/sh -c [command]
来实现的 - Exec 格式底层实现是把命令用空格分隔拼接起来,得到一个新的命令
[command]
,然后直接调用[command]
所以 ENTRYPOINT 和 CMD 组合使用时,应该使用 Exec 格式的命令,否则的话就得不到正确的结果
1 | ENTRYPOINT /bin/ping -c 3 |
从上面看出, 只有 ENTRYPOINT 和 CMD 都用 Exec 格式的命令, 才能得到预期的效果
结论
如果你想让你的 docker image 做真正的工作, 一定会在 Dockerfile 里用到 ENTRYPOINT 或是 CMD。这2个命令不是互斥的. 在很多情况下, 可以组合 ENTRYPOINT 和 CMD 命令, 提升最终用户的体验