Dockerfile 使用技巧

本文翻译自我的英文博客,最新修订内容可随时参考:Dockerfile使用技巧

通常我们会在项目根目录编写 Dockerfile,它是描述镜像构建流程的配置文件。官方文档提供了详细语法说明,但实际使用中需要注意一些关键细节,尤其是 CMDENTRYPOINT 的区别。

一、Dockerfile 核心要点

  1. 多CMD指令
    若定义多个 CMD,仅有最后一个生效。建议将多个命令写入脚本文件(如 start.sh),通过 CMD ["./start.sh"] 执行。

  2. 容器启动机制
    Docker 容器不支持 systemd,其主进程即为应用进程。容器生命周期与主进程绑定,主进程退出则容器终止。

    • 错误示例:CMD service nginx startservice 会在后台运行,导致容器主进程立即退出)。
    • 正确示例:CMD ["nginx", "-g", "daemon off;"](让 Nginx 前台运行成为主进程)。
  3. 构建命令

    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),容器状态与程序生命周期一致。
b. Shell 模式
  • 格式CMD command param1 param2
  • 特点
    • 通过 /bin/sh -c 执行命令,主进程为 Shell(PID 1),目标程序为子进程(PID > 1)。
    • 自动解析环境变量(如 CMD echo $HOME 会输出 /root)。
    • 可能引发信号处理问题(如容器无法捕获程序的退出信号)。

2. CMD 指令

作用:定义容器默认执行的命令或参数,可被 docker run 命令覆盖。

语法形式
  1. Exec 模式(推荐)
    CMD ["python", "app.py"]  # 直接执行 Python 脚本  
    
  2. Shell 模式
    CMD python app.py  # 等价于 CMD ["sh", "-c", "python app.py"]  
    
  3. 为 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 指令

作用:设置容器的固定入口命令,参数可通过 CMDdocker run 动态传递。

语法形式
  1. Exec 模式(推荐)
    ENTRYPOINT ["nginx", "-g", "daemon off;"]  # 前台运行 Nginx,主进程为 Nginx  
    
  2. 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-imagecurl https://example.com
docker run my-image https://baidu.comcurl 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 需前台执行主进程)

四、最佳实践建议

  1. 优先使用 Exec 模式

    • 避免 Shell 模式的隐藏问题(如信号处理、性能损耗)。
    • 需要解析环境变量时,显式通过 sh -c 处理:
      ENTRYPOINT ["sh", "-c", "echo $HOME"]  # 正确解析 $HOME  
      
  2. 明确职责分工

    • ENTRYPOINT:定义不可变的入口命令(如程序二进制文件)。
    • CMD:提供可覆盖的默认参数或备用命令。
  3. 主进程唯一性

    • 确保容器内只有一个主进程(如 Web 服务器、API 服务),避免多个阻塞进程导致容器异常退出。
  4. 构建上下文优化

    • 通过 .dockerignore 排除不必要的文件(如 node_modules、日志文件),减少构建时间和镜像体积。
    *.log  
    node_modules/  
    
  5. 多阶段构建(减少镜像体积)

    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:容器启动后立即退出

  • 原因:主进程执行完毕或崩溃。
  • 排查步骤
    1. 使用 docker run -it my-image sh 进入容器,手动执行主进程命令,查看错误日志。
    2. 确保主进程为前台运行(如去掉 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  
    

总结:选择策略

需求场景推荐指令示例
定义默认执行命令且可覆盖CMDCMD ["python", "app.py"]
固定入口命令,参数可动态传递ENTRYPOINT + CMDENTRYPOINT ["curl"]<br>CMD ["https://example.com"]
需要解析环境变量ENTRYPOINT + shENTRYPOINT ["sh", "-c", "echo $HOME && my-app"]
禁止任何参数覆盖ENTRYPOINTENTRYPOINT ["nginx", "-g", "daemon off;"]

通过合理组合 CMDENTRYPOINT,可以灵活控制容器的启动行为,同时保持镜像的可复用性和可扩展性。更多实战案例可参考博客:Dockerfile使用技巧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timerring

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值