本文翻译自我的英文博客,最新修订内容可随时参考:Dockerfile使用技巧
通常我们会在项目根目录编写 Dockerfile
,它是描述镜像构建流程的配置文件。官方文档提供了详细语法说明,但实际使用中需要注意一些关键细节,尤其是 CMD
和 ENTRYPOINT
的区别。
一、Dockerfile 核心要点
-
多CMD指令:
若定义多个CMD
,仅有最后一个生效。建议将多个命令写入脚本文件(如start.sh
),通过CMD ["./start.sh"]
执行。 -
容器启动机制:
Docker 容器不支持systemd
,其主进程即为应用进程。容器生命周期与主进程绑定,主进程退出则容器终止。- 错误示例:
CMD service nginx start
(service
会在后台运行,导致容器主进程立即退出)。 - 正确示例:
CMD ["nginx", "-g", "daemon off;"]
(让 Nginx 前台运行成为主进程)。
- 错误示例:
-
构建命令:
docker build -t my-image:1.0 . # -t 指定镜像名和标签,. 表示构建上下文(通常为当前目录)
构建上下文会打包目录内文件传递给 Docker 引擎,需避免包含无关文件(可通过
.dockerignore
过滤)。
二、CMD vs ENTRYPOINT:执行模式与区别
1. 执行模式(Exec vs Shell)
a. Exec 模式(推荐)
- 格式:
CMD ["executable", "param1", "param2"]
- 特点:
- 直接执行指定程序,不通过 Shell(如
/bin/sh
)。 - 环境变量需显式传递(如
ENTRYPOINT ["echo", "$HOME"]
不会解析$HOME
)。 - 主进程为目标程序(PID 1),容器状态与程序生命周期一致。
- 直接执行指定程序,不通过 Shell(如
b. Shell 模式
- 格式:
CMD command param1 param2
- 特点:
- 通过
/bin/sh -c
执行命令,主进程为 Shell(PID 1),目标程序为子进程(PID > 1)。 - 自动解析环境变量(如
CMD echo $HOME
会输出/root
)。 - 可能引发信号处理问题(如容器无法捕获程序的退出信号)。
- 通过
2. CMD 指令
作用:定义容器默认执行的命令或参数,可被 docker run
命令覆盖。
语法形式
- Exec 模式(推荐):
CMD ["python", "app.py"] # 直接执行 Python 脚本
- Shell 模式:
CMD python app.py # 等价于 CMD ["sh", "-c", "python app.py"]
- 为 ENTRYPOINT 提供默认参数:
ENTRYPOINT ["curl"] CMD ["https://example.com"] # 可通过 docker run my-image https://baidu.com 覆盖
覆盖规则
- 执行
docker run my-image custom-command
时,CMD
会被custom-command
替换。 - 示例:
CMD ["echo", "hello"] # Dockerfile 中的默认命令
docker run my-image ls # 实际执行 ls(覆盖 CMD)
3. ENTRYPOINT 指令
作用:设置容器的固定入口命令,参数可通过 CMD
或 docker run
动态传递。
语法形式
- Exec 模式(推荐):
ENTRYPOINT ["nginx", "-g", "daemon off;"] # 前台运行 Nginx,主进程为 Nginx
- Shell 模式:
ENTRYPOINT nginx -g "daemon off;" # 主进程为 sh,Nginx 为子进程
参数传递规则
- Exec 模式:
docker run
的参数会追加到ENTRYPOINT
之后。ENTRYPOINT ["curl", "-X"] CMD ["GET"]
docker run my-image POST https://example.com # 实际执行 curl -X POST https://example.com
- Shell 模式:
docker run
的参数会被忽略,仅执行ENTRYPOINT
定义的命令。ENTRYPOINT curl https://example.com
docker run my-image https://baidu.com # 仍执行 curl https://example.com
强制覆盖 ENTRYPOINT
通过 --entrypoint
选项指定新的入口命令:
docker run --entrypoint ls my-image # 忽略 Dockerfile 中的 ENTRYPOINT,执行 ls
三、组合使用 CMD 和 ENTRYPOINT
场景 | 配置示例 | 执行效果 |
---|---|---|
固定命令 + 默认参数 | ENTRYPOINT ["curl"]<br>CMD ["https://example.com"] | docker run my-image → curl https://example.com docker run my-image https://baidu.com → curl https://baidu.com |
禁止参数覆盖 | ENTRYPOINT ["nginx", "-g", "daemon off;"] | 无论 docker run 传递什么参数,始终运行 Nginx 前台模式 |
需要解析环境变量 | ENTRYPOINT ["sh", "-c", "echo $ENV_VAR"] | 正确输出环境变量值(如通过 docker run -e ENV_VAR=test my-image 传递) |
复杂初始化脚本 | ENTRYPOINT ["/app/init.sh"]<br>CMD ["main-process"] | 先执行初始化脚本,再运行主进程(init.sh 需前台执行主进程) |
四、最佳实践建议
-
优先使用 Exec 模式:
- 避免 Shell 模式的隐藏问题(如信号处理、性能损耗)。
- 需要解析环境变量时,显式通过
sh -c
处理:ENTRYPOINT ["sh", "-c", "echo $HOME"] # 正确解析 $HOME
-
明确职责分工:
ENTRYPOINT
:定义不可变的入口命令(如程序二进制文件)。CMD
:提供可覆盖的默认参数或备用命令。
-
主进程唯一性:
- 确保容器内只有一个主进程(如 Web 服务器、API 服务),避免多个阻塞进程导致容器异常退出。
-
构建上下文优化:
- 通过
.dockerignore
排除不必要的文件(如node_modules
、日志文件),减少构建时间和镜像体积。
*.log node_modules/
- 通过
-
多阶段构建(减少镜像体积):
FROM golang:1.20 AS builder WORKDIR /app COPY . . RUN go build -o my-app FROM alpine:3.17 COPY --from=builder /app/my-app /usr/bin/my-app ENTRYPOINT ["my-app"]
五、常见问题与解决方案
问题 1:容器启动后立即退出
- 原因:主进程执行完毕或崩溃。
- 排查步骤:
- 使用
docker run -it my-image sh
进入容器,手动执行主进程命令,查看错误日志。 - 确保主进程为前台运行(如去掉
daemon
参数)。
- 使用
问题 2:环境变量未生效
- 原因:使用 Exec 模式但未通过 Shell 解析。
- 解决方案:
ENTRYPOINT ["sh", "-c", "echo $ENV_VAR && my-app"] # 通过 Shell 解析环境变量
问题 3:无法通过 docker stop 优雅终止容器
- 原因:主进程为 Shell 模式下的子进程,无法接收 SIGTERM 信号。
- 解决方案:改用 Exec 模式,让主进程直接接收信号:
ENTRYPOINT ["my-app"] # 主进程为 my-app,可正确响应 docker stop
总结:选择策略
需求场景 | 推荐指令 | 示例 |
---|---|---|
定义默认执行命令且可覆盖 | CMD | CMD ["python", "app.py"] |
固定入口命令,参数可动态传递 | ENTRYPOINT + CMD | ENTRYPOINT ["curl"]<br>CMD ["https://example.com"] |
需要解析环境变量 | ENTRYPOINT + sh | ENTRYPOINT ["sh", "-c", "echo $HOME && my-app"] |
禁止任何参数覆盖 | ENTRYPOINT | ENTRYPOINT ["nginx", "-g", "daemon off;"] |
通过合理组合 CMD
和 ENTRYPOINT
,可以灵活控制容器的启动行为,同时保持镜像的可复用性和可扩展性。更多实战案例可参考博客:Dockerfile使用技巧。