dockerfile中CMD和ENTRYPOINT指令
1、exec和shell两种风格
无论是CMD还是ENTRYPOINT指令,都支持exec和shell两种风格:
语法 | 示例 | 解析方式 | |
Exec 形式 | JSON 数组 | CMD ["sleep", "60"] | 直接执行二进制程序,docker实际运行execve("sleep", ["sleep", "60"]) |
Shell 形式 | 字符串 | CMD sleep 60 | 通过 shell 执行命令,相当于 /bin/sh -c "sleep 60" |
说明:execve时Linux内核的一个系统调用,作用事用于一个新程序替换当前进程(把新程序加载到当前进程的内存空间,当前的进程会被丢弃)
注意:生产环境中推荐使用Exec形式,更安全、可控。
1)shell风格和exec风格解析方式:
shell风格是通过shell执行的命令,
CMD sleep 60
#Docker实际运行时会变成
/bin/sh -c "sleep 60"
也就是说,它先启动一个 Shell 进程(/bin/sh),再让这个 shell 去执行 sleep 60。所以:
• 容器的 PID 1 进程 实际是 /bin/sh,不是 sleep
• Shell 风格中,可以解释变量、重定向符、管道符等(比如 CMD echo $PATH > /tmp/p)
而对于exec风格是直接执行命令,
CMD ["sleep", "60"]
#DOcker不会提懂/bin/sh,而是直接执行命令
execve("sleep", ["sleep", "60"])
所以,
- 容器内的 PID 1 就是 sleep 进程;
- 命令参数直接传递给进程;
- 不支持 shell 特性(如 $VAR、|、> 等);
- 启动更快、行为更一致。
2)环境变量展开:(CDM和ENTRYPOINT是一样的)
FROM busybox
ENV NAME=KnowLiu# shell 形式
CMD echo "Hello $NAME"
#运行
docker run myimage
# 输出:Hello KnowLiu#-------------
FROM busybox
ENV NAME=KnowLiu
CMD ["echo", "Hello $NAME"]#运行
docker run myimage
# 输出:Hello $NAME
结论:
- Shell 形式支持变量展开;
- Exec 形式不支持(因为没经过 shell)。
3)信号转发:(CDM和ENTRYPOINT是一样的)
FROM busybox
CMD ["sleep", "1000"]#运行,然后停止容器会立即停止
docker run --name test mini
docker stop test#-----------
FROM busybox
CMD sleep 1000#运行,然后停止容器,容器停止会超时
docker run --name test mini
docker stop test
说明:由于/bin/sh是1号进程,SIGTERM 发给 shell,而 shell 默认不会转发信号,导致 sleep 不退出 → 容器停止会超时。
4)参数传递:
#docker file
FROM alpine:3.20
ENTRYPOINT ["echo", "hahaha!"]#构建、运行
docker build -t my-test .
docker run my-test
输出 hahaha!docker run my-test hi
输出 hahaha! hidocker run my-test echo hi
输出 hahaha! echo hi #------------------------
#docker file
FROM alpine:3.20
ENTRYPOINT echo hahaha#构建、运行
docker build -t my-test .
docker run my-test
输出 hahahadocker run my-test hi
输出 hahahadocker run my-test echo hi
输出 hahaha
可以看到,ENTRYPOINT指令的exec风格支持传递参数,shell风格不支持!关于命令覆盖,见下面!
5)总结二者的区别:
特性 | Exec 形式 | Shell 形式 |
写法 | CMD ["cmd", "arg1", "arg2"] ENTRYPOINT ["cmd", "arg1", "arg2"] | CMD cmd arg1 arg2 ENTRYPOINT cmd arg1 arg2 |
是否通过 /bin/sh | ❌ 否 | ✅ 是 |
是否支持环境变量展开 | ❌ 否 | ✅ 是 |
是否支持管道、重定向等 | ❌ 否 | ✅ 是 |
信号传递(SIGTERM 等) | ✅ 正常 | ⚠️ 可能被拦截 |
运行时参数(docker run 参数) | ✅ 支持,仅针对对ENTRYPOINT指令,CMD指令是命令覆盖 | ❌ 否,仅针对对ENTRYPOINT指令,CMD指令是命令覆盖 |
PID 1 是谁 | 应用进程 | /bin/sh |
推荐场景 | 长期运行的服务、生产环境 | 临时命令、调试脚本 |
性能 | 更快、更干净 | 稍慢 |
2、CMD、ENTRYPOINT指令
1)基本:
二者都是用来提供容器启动时的命令,都支持exec和shell两种书写风格:
- CMD提供的是默认命令,所以支持命令的覆盖;此外,还可以作为为 ENTRYPOINT 指令提供默认参数(ENTRYPOINT+CMD组合方式,并且使用exec风格)
- ENTRYPOINT是固定的命令,不支持命令覆盖,可以进行参数的传递。
ENTRYPOINT虽然默认不能命令的覆盖,但是可以通过--entrypoint参数来实现:
docker run --entrypoint <新命令> <镜像名> [参数...]
看个例子
#dockerfile
FROM alpine:3.20
ENTRYPOINT ["echo", "hahaha!"]#构建
docker build -t mytest
#运行
docker run mytest
#输出 hahaha!docker run --entrypoint ls mytest /usr
输出
bin
lib
local
sbin
share
注意:在dockerfile中如果有多个CMD、ENTRYPOING指令,那么最后一个会覆盖前面的。
2)一句话区分CMD和ENTRYPOINT:
- CMD:提供容器启动的默认命令或参数,可以被覆盖(被entrypoint覆盖,或者被docker run后面跟的命令覆盖)
- ENTRYPOINT:定义容器启动时固定要执行的主命令,一般不会覆盖(除非使用--entrypoint)
示例1:使用CMD:
#dockerfile
FROM busybox
CMD ["echo", "hello world"]#构建
docker build -t mytest
#运行
docker run mytest
#输出 hello world# 运行
docker run myimage echo hi
# 输出:hi
说明:CMD 定义的命令可以被你在 docker run 中输入的新命令替换掉。
示例2:ENTRYPOINT示例:
#dockerfile
FROM busybox
ENTRYPOINT ["echo", "hello"]# 运行
docker run myimage
# 输出:hello#运行
docker run myimage world
# 输出:hello world
说明:这里 ENTRYPOINT 是固定命令 echo,而 docker run myimage world 中的参数 world 会被当作 ENTRYPOINT 的参数。
示例3:你可以 组合 ENTRYPOINT + CMD 来实现“固定命令 + 默认参数”模式
#dockerfile
FROM busybox
ENTRYPOINT ["sleep"]
CMD ["60"]#运行
docker run myimage
# 等价于:sleep 60docker run myimage 10
# 等价于:sleep 10
说明:
- ENTRYPOINT 是固定的命令:sleep
- CMD 提供默认参数:60
- 用户在 docker run 中输入参数会覆盖 CMD,但不会影响 ENTRYPOINT
3、docker run运行容器时的命令覆盖和参数传递
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
[COMMAND] 和 [ARG...] 到底是「命令覆盖」还是「参数传递」,取决于 Dockerfile 的 ENTRYPOINT/CMD 组合形式。
Dockerfile 内容 | 启动行为 | docker run IMAGE ... 的效果 |
1️⃣ 只有 CMD | CMD 是默认命令 | 运行时的 [COMMAND] 会 覆盖 CMD |
2️⃣ 只有 ENTRYPOINT | ENTRYPOINT 是固定命令 | 对于exec风格下,运行时的 [COMMAND] 会被 当作参数传递给 ENTRYPOINT |
3️⃣ ENTRYPOINT + CMD | ENTRYPOINT 是固定命令,CMD 提供默认参数 | 对于exec风格下,运行时的 [COMMAND] 会 覆盖 CMD(参数部分),但 不会覆盖 ENTRYPOINT |
1)示例1:CMD 的shell和exec风格:
#docker file
FROM alpine:3.20
CMD echo hello#构建、运行
docker build -t my-test .
docker run my-test
输出 hellodocker run my-test echo hi
输出 hidocker run my-test hi
报错,“hi” executable file not found in $PATH: unknown.
说明:将dockerfile替换成CMD ["echo", "hello"] 运行结果一样!所以,对于CMD指令来说,只有ENTRYPOINT + CMD的组合的时候,才能通过docker run传递参数给CMD,只有CMD的时候,传递的“字符串”都是当命令来执行的,无法单独为其传递参数!
2)示例2:ENTRYPOINT的shell风格:
#docker file
FROM alpine:3.20
ENTRYPOINT echo hahaha#构建、运行
docker build -t my-test .
docker run my-test
输出 hahahadocker run my-test hi
输出 hahahadocker run my-test echo hi
输出 hahaha
说明:对于ENTRYPOINT的shell风格,无法传递参数!
3)示例3:ENTRYPOINT的exec风格:
#docker file
FROM alpine:3.20
ENTRYPOINT ["echo", "hahaha!"]#构建、运行
docker build -t my-test .
docker run my-test
输出 hahaha!docker run my-test hi
输出 hahaha! hidocker run my-test echo hi
输出 hahaha! echo hi
说明:对于ENTRYPOINT的Exec风格,传递的字符串会当作参数传递,并追加进去!
4)示例4:组合模式exec风格
#docker file
FROM alpine:3.20
ENTRYPOINT ["echo"]
CMD ["hi"]#构建、运行
docker build -t my-test .
docker run my-test
输出 hidocker run my-test abc
输出 abcdocker run my-test echo 123
输出 echo 123
说明:docker run后面的字符串,会当作参数覆盖CMD。
5)示例5:组合模式shell风格
#docker file
FROM alpine:3.20
ENTRYPOINT echo
CMD hi#构建、运行
docker build -t my-test .
docker run my-test
输出 空docker run my-test abc
输出 空docker run my-test echo 123
输出 空
说明:docker官方文档描述:When using the exec form of ENTRYPOINT, the CMD value is passed as arguments to ENTRYPOINT.When using the shell form of ENTRYPOINT, the CMD value is ignored. 也就是说, Shell 形式的 ENTRYPOINT 会忽略 CMD 的内容。