# Dockerfile中CMD与ENTRYPOINT * **`ENTRYPOINT` 是“命令”**:它设置了容器启动时**必须**执行的主程序,这个设定很难被改变。 * **`CMD` 是“默认参数”**:它为上面的“命令”提供**默认的**参数。如果用户在运行容器时提供了其他参数,`CMD` 提供的默认值就会被替换掉。 下面这个表格可以让你更直观地看到它们的不同: | 特性 | `CMD` | `ENTRYPOINT` | | :--- | :--- | :--- | | **核心作用** | 提供默认的命令或参数,容易被覆盖 | 设定容器的主程序,确保其始终运行 | | **参数覆盖** | 被 `docker run` 后面的**全部参数覆盖** | 被 `docker run` 后面的参数**追加**到其命令末尾 | | **覆盖方式** | `docker run ` 完全替换 `CMD` | 除非使用 `--entrypoint` 标志,否则无法轻易覆盖 | ## 📌 用法对比与示例 光看理论还不够直观,我们来看看具体例子。 ### 1. 只用 `CMD`:容易被“替换”的默认行为 假设 Dockerfile 里写着 `CMD ["ls"]`。 * 当你运行 `docker run my-image` 时,容器会执行 `ls`,列出目录内容。 * 如果你运行 `docker run my-image -l`,这时 `docker run` 后面的 `-l` 会**覆盖**掉 `CMD` 指定的 `ls`,导致容器尝试执行 `-l` 这个命令,从而报错。这是因为 `ls` 这个命令被完全替换掉了。 ### 2. 只用 `ENTRYPOINT`:固定命令,参数可追加 假设 Dockerfile 里写着 `ENTRYPOINT ["ls"]`。 * 当你运行 `docker run my-image` 时,效果和上面一样,执行 `ls`。 * 当你运行 `docker run my-image -l` 时,`-l` 会被**追加**到 `ENTRYPOINT` 命令的后面,最终执行的命令是 `ls -l`。这能成功实现你想要的详细列表输出。 ### 3. 强强联手:`ENTRYPOINT` + `CMD`(推荐做法) 这是一种很常用也推荐的做法,结合了二者的优点:用 `ENTRYPOINT` 固定主程序,用 `CMD` 提供灵活的默认参数。 ```dockerfile ENTRYPOINT ["curl"] CMD ["https://example.com"] ``` 在这个例子里: * 运行 `docker run my-image`,实际执行 `curl https://example.com`。 * 运行 `docker run my-image https://api.github.com`,`CMD` 的默认 URL 被覆盖,实际执行 `curl https://api.github.com`。 ## 🛠️ 如何强制覆盖 `ENTRYPOINT`? 虽然 `ENTRYPOINT` 被设计为不容易被覆盖,但确实有一个方法:在 `docker run` 时使用 `--entrypoint` 标志。 例如,无论 Dockerfile 里的 `ENTRYPOINT` 是什么,执行下面的命令都会让容器运行 `sh` 命令: ```bash docker run --entrypoint sh my-image ``` 这通常用于调试或紧急情况下,临时进入容器内部。 ### ⚠️ 两种格式的选择:Exec 格式 vs Shell 格式 使用 `CMD` 和 `ENTRYPOINT` 时,有两种写法,强烈建议优先使用 **Exec 格式**(即带中括号的写法)。 * **Exec 格式**:`CMD ["executable", "param1", "param2"]` 或 `ENTRYPOINT ["executable", "param1"]`。这是推荐写法,它会直接执行程序,信号处理正常。 * **Shell 格式**:`CMD command param` 或 `ENTRYPOINT command param`。这种写法会通过 `/bin/sh -c` 启动一个子进程来执行命令,可能导致容器无法正常收到退出信号,并且在 `ENTRYPOINT` 下,`docker run` 的追加参数可能失效。