CMD定义容器启动默认行为,其生效取决于是否配合ENTRYPOINT及运行时参数:Exec格式直接执行二进制(PID 1为主进程,推荐生产使用);Shell格式经/bin/sh -c包装(适合调试但信号易丢失);参数格式必须搭配ENTRYPOINT才有效。

CMD 指令本身不执行命令,它只定义容器启动时的默认行为——这个行为是否生效、如何生效,取决于你有没有配合 ENTRYPOINT,以及运行时有没有传参。直接写 CMD 不等于“一跑就执行”,关键在格式和组合逻辑。
明确 CMD 的三种写法及适用场景
CMD 有且只有三种合法写法,每种行为完全不同:
-
Exec 格式(推荐生产使用):
CMD ["java", "-jar", "app.jar"]—— 直接执行二进制,PID 1 就是主进程,信号能正常接收; -
Shell 格式:
CMD java -jar app.jar --server.port=8080—— 实际被包装成/bin/sh -c "...",适合简单调试,但 PID 1 是 sh,容易导致信号转发失败; -
参数格式:
CMD ["--debug", "--spring.profiles.active=prod"]—— 必须搭配 ENTRYPOINT 才有效,单独写会被忽略或报错。
用 CMD 配合 ENTRYPOINT 实现可覆盖的默认参数
这是最实用、最可控的配置方式:ENTRYPOINT 固定主程序,CMD 提供默认参数,用户运行时还能灵活覆盖。
- 例如 Spring Boot 应用:
ENTRYPOINT ["java", "-jar", "/app.jar"]<br> CMD ["--server.port=8080", "--logging.level.root=WARN"]
运行docker run myapp启动端口 8080;运行docker run myapp --server.port=9000就自动替换端口; - 注意:两个指令都必须用 JSON 数组格式(双引号包裹每个项),否则参数会被合并成单个字符串,Spring Boot 无法识别;
- 如果 ENTRYPOINT 是脚本(如
["/entrypoint.sh"]),CMD 参数会传给脚本,可在脚本中做环境检查、配置生成等,最后用exec "$@"启动服务,确保 PID 1 正确。
避免 CMD 失效的几个硬性细节
很多“CMD 没起作用”其实是配置或理解偏差:
- Dockerfile 中多个 CMD 只有最后一个生效,别分散写;
- 运行容器时带命令(如
docker run myimg bash)会完全覆盖 CMD,哪怕只写一个单词; - CMD 不能用于初始化操作(如创建目录、改权限),这些必须放在 RUN 或 ENTRYPOINT 脚本里;
- exec 格式下不解析环境变量(如
["$HOME"]无效),要用 shell 包一层:["sh", "-c", "echo $HOME"]; - 如果基础镜像已有 CMD(如
node:alpine默认是["node"]),你的 CMD 会覆盖它——这是设计行为,不是 bug。
验证 CMD 是否按预期生效的小技巧
别只靠日志猜,动手确认更可靠:
- 构建后查看镜像配置:
docker inspect myimg | grep -A 3 Cmd,看输出是否是你写的数组形式; - 运行容器后查进程:
docker exec -it <id> ps aux,确认 PID 1 是不是你期望的程序; - 测试参数覆盖:
docker run myimg --help,观察是否把 --help 传给了主程序而不是被忽略。


















