> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/docker_practice/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/docker_practice/di-er-bu-fen-jin-jie-pian/07_dockerfile/7.4_cmd.md).

# 7.4 CMD 容器启动命令

## 何时使用 CMD，何时使用 ENTRYPOINT？

在深入 CMD 的细节之前，我们需要理解一个关键问题：**CMD 和 ENTRYPOINT 应该在什么时候使用？**

这是 Dockerfile 使用中最常见的困惑之一。简单的答案是：

* **CMD**：定义容器的“默认命令”。如果用户在 `docker run` 时提供命令，CMD 会被覆盖
* **ENTRYPOINT**：定义容器的“入口脚本”。通常用于启动应用的某个特定部分

**决策树**：

1. **你的容器是否有不可变的启动逻辑？** 比如需要先做一些初始化工作，然后才运行应用 → 使用 ENTRYPOINT
2. **用户经常会在 `docker run` 时传入不同的命令吗？** → 使用 CMD（让用户灵活覆盖）
3. **大多数情况下，你希望容器始终以相同的方式启动？** → 使用 ENTRYPOINT

更多细节见 [7.5 ENTRYPOINT 指令](/docker_practice/di-er-bu-fen-jin-jie-pian/07_dockerfile/7.5_entrypoint.md)。

## 7.4.1 什么是 CMD

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

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

***

## 7.4.2 语法格式

CMD 有三种格式：

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

### exec 格式：推荐

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

**优点**：

* 直接执行指定程序，是容器的 PID 1
* 正确接收信号（如 SIGTERM）
* 无需 shell 解析

### shell 格式

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

**实际执行**：会被包装为 `sh -c`

```docker
## 你写的

CMD echo $HOME

## 实际执行的

CMD ["sh", "-c", "echo $HOME"]
```

**优点**：可以使用 `$HOME` 这类 shell 变量展开、管道等 shell 特性

**缺点**：主进程是 sh，信号无法正确传递给应用

***

## 7.4.3 exec 格式 vs shell 格式

| 特性              | exec 格式            | shell 格式     |
| --------------- | ------------------ | ------------ |
| 主进程             | 指定的程序              | `/bin/sh`    |
| 信号传递            | ✅ 正确               | ❌ 无法传递       |
| `$VAR` shell 展开 | ❌ 不自动展开；环境变量仍会传入进程 | ✅ 自动展开       |
| 推荐使用            | ✅ 大多数场景            | 需要 shell 特性时 |

### 信号传递问题示例

```docker
## ❌ shell 格式：docker stop 会超时

CMD node server.js

## 实际是 sh -c "node server.js"

## SIGTERM 发给 sh，不会传递给 node

## ✅ exec 格式：docker stop 正常工作

CMD ["node", "server.js"]

## SIGTERM 直接发给 node

...
```

***

## 7.4.4 运行时覆盖 CMD

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

```bash
## ubuntu 默认 CMD 是 /bin/bash

$ docker run -it ubuntu        # 进入 bash
$ docker run ubuntu cat /etc/os-release  # 覆盖为 cat 命令
```

```bash
Dockerfile:              docker run 命令:
CMD ["/bin/bash"]   +    cat /etc/os-release
        │                        │
        └───────► 被覆盖 ◄───────┘
                    ↓
           执行: cat /etc/os-release
```

***

## 7.4.5 经典错误：容器立即退出

### 错误示例

```docker
## ❌ 容器启动后立即退出

CMD service nginx start
```

### 原因分析

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

### 正确做法

```docker
## ✅ 让 nginx 在前台运行

CMD ["nginx", "-g", "daemon off;"]
```

***

## 7.4.6 CMD vs ENTRYPOINT

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

### 单独使用 CMD

```docker
## Dockerfile

CMD ["curl", "-s", "http://example.com"]
```

```bash
$ docker run myimage              # 执行默认命令
$ docker run myimage curl -v ...  # 完全覆盖
```

### 搭配 ENTRYPOINT

```docker
## Dockerfile

ENTRYPOINT ["curl", "-s"]
CMD ["http://example.com"]
```

```bash
$ docker run myimage              # curl -s http://example.com
$ docker run myimage http://other.com  # curl -s http://other.com（参数覆盖）
```

详见 [ENTRYPOINT 入口点](/docker_practice/di-er-bu-fen-jin-jie-pian/07_dockerfile/7.5_entrypoint.md)章节。

***

## 7.4.7 最佳实践

### 1. 优先使用 exec 格式

```docker
## ✅ 推荐

CMD ["python", "app.py"]

## ⚠️ 仅在需要 shell 特性时使用

CMD ["sh", "-c", "echo $PATH && python app.py"]
```

### 2. 确保应用在前台运行

```docker
## ✅ 前台运行

CMD ["nginx", "-g", "daemon off;"]
CMD ["apache2ctl", "-D", "FOREGROUND"]
CMD ["java", "-jar", "app.jar"]

## ❌ 不要使用后台服务命令

CMD service nginx start
CMD systemctl start nginx
```

### 3. 使用双引号

```docker
## ✅ 正确：双引号

CMD ["node", "server.js"]

## ❌ 错误：单引号（JSON 不支持）

CMD ['node', 'server.js']
```

### 4. 配合 ENTRYPOINT 使用

```docker
## 用于可配置参数的场景

ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]

## 运行时可以覆盖端口

$ docker run myapp --port 9000

...
```

***

## 7.4.8 常见问题

### Q：CMD 可以写多个吗？

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

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

### Q：如何在 CMD 中使用环境变量？

```docker
## 方法1：使用 shell 格式

CMD echo "Port is $PORT"

## 方法2：显式使用 sh -c

CMD ["sh", "-c", "echo Port is $PORT"]
```

### Q：为什么我的容器不响应 Ctrl+C？

可能是使用了 shell 格式，信号被 sh 吃掉了：

```docker
## ❌ 信号无法传递

CMD python app.py

## ✅ 信号正确传递

CMD ["python", "app.py"]
```

***
