Files
docker_practice/image/dockerfile/cmd.md
2026-01-30 16:48:39 -08:00

5.5 KiB
Raw Blame History

CMD 容器启动命令

什么是 CMD

CMD 指令用于指定容器启动时默认执行的命令。它定义了容器的"主进程"。

核心概念:容器的生命周期 = 主进程的生命周期。CMD 指定的命令就是这个主进程。


语法格式

CMD 有三种格式:

格式 语法 推荐程度
exec 格式 CMD ["可执行文件", "参数1", "参数2"] 推荐
shell 格式 CMD 命令 参数1 参数2 ⚠️ 简单场景
参数格式 CMD ["参数1", "参数2"] 配合 ENTRYPOINT

exec 格式(推荐)

CMD ["nginx", "-g", "daemon off;"]
CMD ["python", "app.py"]
CMD ["node", "server.js"]

优点

  • 直接执行指定程序,是容器的 PID 1
  • 正确接收信号(如 SIGTERM
  • 无需 shell 解析

shell 格式

CMD echo "Hello World"
CMD nginx -g "daemon off;"

实际执行:会被包装为 sh -c

# 你写的
CMD echo $HOME

# 实际执行的
CMD ["sh", "-c", "echo $HOME"]

优点:可以使用环境变量、管道等 shell 特性 缺点:主进程是 sh信号无法正确传递给应用


exec 格式 vs shell 格式

特性 exec 格式 shell 格式
主进程 指定的程序 /bin/sh
信号传递 正确 无法传递
环境变量 需要 shell 包装 自动解析
推荐使用 大多数场景 需要 shell 特性时

信号传递问题示例

# ❌ shell 格式docker stop 会超时
CMD node server.js
# 实际是 sh -c "node server.js"
# SIGTERM 发给 sh不会传递给 node

# ✅ exec 格式docker stop 正常工作
CMD ["node", "server.js"]
# SIGTERM 直接发给 node

运行时覆盖 CMD

docker run 后的命令会覆盖 Dockerfile 中的 CMD

# ubuntu 默认 CMD 是 /bin/bash
$ docker run -it ubuntu        # 进入 bash
$ docker run ubuntu cat /etc/os-release  # 覆盖为 cat 命令
Dockerfile:              docker run 命令:
CMD ["/bin/bash"]   +    cat /etc/os-release
        │                        │
        └───────► 被覆盖 ◄───────┘
                    ↓
           执行: cat /etc/os-release

经典错误:容器立即退出

错误示例

# ❌ 容器启动后立即退出
CMD service nginx start

原因分析

1. CMD service nginx start
   ↓ 被转换为
2. CMD ["sh", "-c", "service nginx start"]
   ↓
3. sh 启动,执行 service 命令
   ↓
4. service 命令将 nginx 放到后台
   ↓
5. service 命令结束sh 退出
   ↓
6. 容器主进程sh退出 → 容器停止

正确做法

# ✅ 让 nginx 在前台运行
CMD ["nginx", "-g", "daemon off;"]

CMD vs ENTRYPOINT

指令 用途 运行时行为
CMD 默认命令 docker run 参数会覆盖
ENTRYPOINT 入口点 docker run 参数会追加到它后面

单独使用 CMD

# Dockerfile
CMD ["curl", "-s", "http://example.com"]
$ docker run myimage              # 执行默认命令
$ docker run myimage curl -v ...  # 完全覆盖

搭配 ENTRYPOINT

# Dockerfile
ENTRYPOINT ["curl", "-s"]
CMD ["http://example.com"]
$ docker run myimage              # curl -s http://example.com
$ docker run myimage http://other.com  # curl -s http://other.com参数覆盖

详见 ENTRYPOINT 入口点 章节。


最佳实践

1. 优先使用 exec 格式

# ✅ 推荐
CMD ["python", "app.py"]

# ⚠️ 仅在需要 shell 特性时使用
CMD ["sh", "-c", "echo $PATH && python app.py"]

2. 确保应用在前台运行

# ✅ 前台运行
CMD ["nginx", "-g", "daemon off;"]
CMD ["apache2ctl", "-D", "FOREGROUND"]
CMD ["java", "-jar", "app.jar"]

# ❌ 不要使用后台服务命令
CMD service nginx start
CMD systemctl start nginx

3. 使用双引号

# ✅ 正确:双引号
CMD ["node", "server.js"]

# ❌ 错误单引号JSON 不支持)
CMD ['node', 'server.js']

4. 配合 ENTRYPOINT 使用

# 用于可配置参数的场景
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]

# 运行时可以覆盖端口
$ docker run myapp --port 9000

常见问题

Q: CMD 可以写多个吗?

不可以。多个 CMD 只有最后一个生效:

CMD ["echo", "first"]
CMD ["echo", "second"]  # 只有这个生效

Q: 如何在 CMD 中使用环境变量?

# 方法1使用 shell 格式
CMD echo "Port is $PORT"

# 方法2显式使用 sh -c
CMD ["sh", "-c", "echo Port is $PORT"]

Q: 为什么我的容器不响应 Ctrl+C

可能是使用了 shell 格式,信号被 sh 吃掉了:

# ❌ 信号无法传递
CMD python app.py

# ✅ 信号正确传递
CMD ["python", "app.py"]

本章小结

要点 说明
作用 指定容器启动时的默认命令
推荐格式 exec 格式 CMD ["程序", "参数"]
覆盖方式 docker run image 新命令
与 ENTRYPOINT CMD 作为 ENTRYPOINT 的默认参数
核心原则 应用必须在前台运行

延伸阅读