6月20日 11:19

Dockerfile CMD 和 ENTRYPOINT 有什么区别?

Dockerfile 里的 CMDENTRYPOINT 都和容器启动命令有关,但职责不一样:ENTRYPOINT 更像“固定要运行的程序”,CMD 更像“默认参数”或“默认命令”。

如果只记一句话:想让镜像像一个可执行程序一样运行,用 ENTRYPOINT;想给容器提供一个可以轻松替换的默认启动命令,用 CMD。两者一起用时,通常是 ENTRYPOINT 写可执行文件,CMD 写默认参数。

CMD 是什么?

CMD 用来指定容器启动时的默认命令。它最大的特点是:容易被 docker run 后面的参数覆盖

常见写法有三种:

dockerfile
CMD ["node", "server.js"] CMD node server.js CMD ["--help"]

前两种是完整命令,第三种通常配合 ENTRYPOINT 使用,表示给 ENTRYPOINT 传默认参数。

比如:

dockerfile
FROM node:20-alpine WORKDIR /app COPY . . CMD ["node", "server.js"]

直接运行:

bash
docker run my-node-app

实际执行的是:

bash
node server.js

但如果这样运行:

bash
docker run my-node-app node worker.js

CMD ["node", "server.js"] 会被 node worker.js 覆盖。所以 CMD 适合放“可以被用户改掉的默认行为”。

ENTRYPOINT 是什么?

ENTRYPOINT 用来指定容器启动时固定执行的程序。普通的 docker run 参数不会直接覆盖它,而是会追加到 ENTRYPOINT 后面,作为参数传入。

dockerfile
FROM alpine ENTRYPOINT ["ping"] CMD ["localhost"]

运行:

bash
docker run ping-image

等价于:

bash
ping localhost

运行:

bash
docker run ping-image 8.8.8.8

等价于:

bash
ping 8.8.8.8

这里 ping 是固定程序,localhost 只是默认参数。用户传了 8.8.8.8 后,覆盖的是 CMD,不是 ENTRYPOINT

exec 形式和 shell 形式有什么区别?

CMDENTRYPOINT 都有 exec 形式和 shell 形式。

exec 形式写成 JSON 数组:

dockerfile
ENTRYPOINT ["node", "server.js"] CMD ["--port", "3000"]

shell 形式写成普通字符串:

dockerfile
ENTRYPOINT node server.js CMD node server.js

推荐优先用 exec 形式,原因有两个。

第一,exec 形式不会额外套一层 shell,参数传递更准确。第二,exec 形式对信号处理更友好。容器里的主进程通常是 PID 1,Docker 停止容器时会先发送 SIGTERM。如果用 shell 形式,真正的业务进程可能变成 shell 的子进程,信号不一定能正确传过去,容易出现容器停止慢、进程残留、优雅退出失效等问题。

dockerfile
ENTRYPOINT node server.js

实际可能是:

bash
/bin/sh -c "node server.js"

更推荐:

dockerfile
ENTRYPOINT ["node", "server.js"]

如果确实需要 shell 能力,比如环境变量展开、管道、&&,可以使用 shell 形式,但要知道它带来的信号处理问题。生产镜像里更稳妥的方式通常是把复杂逻辑放到脚本里,并在脚本最后使用 exec

sh
#!/bin/sh set -e exec node server.js

再配合:

dockerfile
ENTRYPOINT ["./entrypoint.sh"]

docker run 怎么覆盖它们?

覆盖 CMD 很简单,直接在镜像名后面追加命令或参数。

bash
docker run my-image bash

如果镜像只有:

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

那么 bash 会覆盖原来的 CMD

如果镜像是:

dockerfile
ENTRYPOINT ["node", "server.js"] CMD ["--port", "3000"]

执行:

bash
docker run my-image --port 8080

最终命令是:

bash
node server.js --port 8080

如果连 ENTRYPOINT 也想覆盖,需要显式使用 --entrypoint

bash
docker run --entrypoint sh my-image

注意,--entrypoint 只替换入口程序,镜像原来的 CMD 仍可能作为参数拼到后面。排查问题时可以看镜像的 Config.EntrypointConfig.Cmd

bash
docker inspect my-image

什么时候只用 CMD?

如果镜像只是给一个默认启动命令,用户经常需要替换整个命令,用 CMD 就够了。

dockerfile
FROM node:20-alpine WORKDIR /app COPY . . CMD ["npm", "run", "dev"]

默认启动开发服务,但用户也可以方便地执行:

bash
docker run my-app npm test

这种场景下,CMD 的可覆盖特性反而是优点。

什么时候用 ENTRYPOINT + CMD?

如果希望镜像表现得像一个固定工具,推荐 ENTRYPOINT + CMD

dockerfile
FROM alpine RUN apk add --no-cache curl ENTRYPOINT ["curl"] CMD ["--help"]

默认运行会输出 curl 帮助;传入参数时:

bash
docker run curl-image -I https://example.com

实际执行:

bash
curl -I https://example.com

服务型镜像也可以这样写:

dockerfile
ENTRYPOINT ["node", "server.js"] CMD ["--port", "3000"]

这样镜像默认监听 3000,用户需要改端口时只覆盖参数即可。

docker compose 里的 command 和 entrypoint 对应什么?

在 Docker Compose 里,command 对应 Dockerfile 里的 CMDentrypoint 对应 Dockerfile 里的 ENTRYPOINT

yaml
services: app: image: my-app command: ["--port", "8080"]

如果镜像里有:

dockerfile
ENTRYPOINT ["node", "server.js"] CMD ["--port", "3000"]

Compose 的 command 会把默认参数改成:

bash
node server.js --port 8080

如果同时覆盖入口和参数:

yaml
services: app: image: my-app entrypoint: ["sh"] command: ["-c", "env && sleep 3600"]

这常用于临时调试。

常见写法怎么选?

场景推荐写法原因
默认启动一个服务,用户可能替换整条命令CMD覆盖方便
镜像就是一个 CLI 工具ENTRYPOINT + CMD固定工具名,参数可变
服务程序固定,只想允许改参数ENTRYPOINT + CMD程序稳定,参数灵活
需要临时进入容器排查--entrypoint sh直接绕过原入口
需要信号正确传给主进程exec 形式避免 shell 吞信号

实际项目里可以这样记:CMD 管默认值,ENTRYPOINT 管主程序;exec 形式优先,shell 形式慎用;Compose 里的 command 改 CMD,entrypoint 改 ENTRYPOINT。

标签:Docker