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! hi

docker 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 
输出 hahaha

docker run my-test hi
输出 hahaha

docker 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 60

docker 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 输出 hello docker run my-test echo hi 输出 hi docker 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 输出 hahaha docker run my-test hi 输出 hahaha docker 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! hi docker 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 输出 hi docker run my-test abc 输出 abc docker 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 的内容。

相关推荐
安卓开发者3 小时前
Docker常用镜像使用指南:从入门到实战
运维·docker·容器
霖.244 小时前
Docker常见问题
服务器·docker·云原生·容器
嫄码5 小时前
Docker部署RocketMQ时Broker IP地址问题及解决方案
tcp/ip·docker·rocketmq
深蓝电商API5 小时前
爬虫+Docker:让你的爬虫项目一键部署、可移植
爬虫·docker·容器
ZHE|张恒6 小时前
使用 Docker 容器测试端口开放性
运维·docker·容器
切糕师学AI7 小时前
云原生技术栈解析:宿主机、容器、Docker、Kubernetes 之间的区别于联系
docker·云原生·容器·kubernetes
java_logo9 小时前
Docker 部署 MinIO 全指南
运维·windows·mongodb·docker·容器
我狸才不是赔钱货9 小时前
DevOps:打破开发与运维之间的高墙
运维·vscode·docker·devops
黄雄进10 小时前
Windows使用docker安装milvus的配置文件
windows·docker·milvus