当前位置: 首页 > news >正文

在 Docker 中,如何构建多阶段镜像以减少镜像体积?

在 Docker 中,多阶段构建(Multi-Stage Builds)是减少镜像体积、提高安全性的最有效技术。它允许你在一个Dockerfile中使用多个FROM语句,将构建环境(如编译器、SDK、构建工具)与最终运行环境分离,只将必要的产物复制到最终镜像中。


一、核心原理

传统的 Docker 构建方式会将所有构建步骤产生的文件都保留在最终镜像中,导致镜像体积巨大(例如包含gcc,make,maven,node_modules等)。

多阶段构建的工作流程

  1. 构建阶段(Builder Stage):使用包含完整开发工具链的基础镜像(如golang:1.21,maven:3.8,node:18),编译代码、安装依赖。
  2. 运行阶段(Final Stage):使用极简的基础镜像(如alpine,distroless,scratch),仅从构建阶段复制编译好的二进制文件或静态资源。
  3. 结果:最终镜像不包含任何构建工具、源代码或中间文件,体积极小。

二、基础语法

Dockerfile中,通过给FROM指令指定AS别名来定义阶段:

# 阶段 1:构建 FROM <构建基础镜像> AS builder # ... 构建命令 ... # 阶段 2:运行 FROM <运行基础镜像> # 从阶段 1 复制文件 COPY --from=builder <源路径> <目标路径> # ... 运行命令 ...

三、实战案例

案例 1:Go 语言应用(从 800MB+ 减少到 10MB+)

传统方式(体积大)

FROM golang:1.21 WORKDIR /app COPY . . RUN go build -o main . CMD ["./main"] # 镜像包含 Go 编译器、源码、构建缓存,体积约 800MB+

多阶段构建(体积极小)

# 阶段 1:构建 FROM golang:1.21-alpine AS builder WORKDIR /app # 安装 git 等构建依赖(如果需要拉取依赖) RUN apk add --no-cache git COPY . . # 编译静态二进制文件 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . # 阶段 2:运行 FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ # 仅从 builder 阶段复制编译好的二进制文件 COPY --from=builder /app/main . # 暴露端口 EXPOSE 8080 CMD ["./main"]
  • 效果:最终镜像仅包含alpine基础库和二进制文件,体积通常< 15MB
  • 关键点CGO_ENABLED=0确保生成静态链接的二进制文件,无需在运行镜像中安装 glibc。
案例 2:Java (Maven/Gradle) 应用

Java 应用通常构建慢且依赖多,多阶段构建可以分离构建环境和运行环境。

# 阶段 1:构建 FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /app COPY pom.xml . # 下载依赖(利用 Docker 缓存层) RUN mvn dependency:go-offline -B COPY src ./src # 打包 RUN mvn package -DskipTests # 阶段 2:运行 FROM eclipse-temurin:17-jre-alpine WORKDIR /app # 仅复制 jar 包 COPY --from=builder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]
  • 效果:最终镜像只包含 JRE 和 Jar 包,去除了 Maven、源码和构建缓存。
案例 3:Node.js 前端应用(React/Vue)

前端构建需要node_modules和构建工具,但运行只需要静态文件。

# 阶段 1:构建 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . # 构建生产版本 RUN npm run build # 阶段 2:运行 (使用 Nginx 托管静态文件) FROM nginx:alpine # 复制构建产物到 Nginx 目录 COPY --from=builder /app/dist /usr/share/nginx/html # 可选:复制自定义 nginx 配置 COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
  • 效果:最终镜像只有 Nginx 和静态 HTML/CSS/JS,没有node_modulesnpm或源代码。
案例 4:Python 应用
# 阶段 1:构建 FROM python:3.11-slim AS builder WORKDIR /app COPY requirements.txt . # 安装依赖到临时目录 RUN pip install --user --no-cache-dir -r requirements.txt # 阶段 2:运行 FROM python:3.11-slim WORKDIR /app # 复制用户安装的包 COPY --from=builder /root/.local /root/.local # 确保路径包含 ENV PATH=/root/.local/bin:$PATH COPY . . CMD ["python", "main.py"]

四、高级技巧

1. 使用distroless镜像(极致安全与体积)

Google 的distroless镜像只包含运行应用所需的文件和依赖,没有 shell、包管理器等。

FROM golang:1.21 AS builder # ... 构建 ... # 使用 distroless 作为运行镜像 FROM gcr.io/distroless/static-debian11 COPY --from=builder /app/main . CMD ["./main"]
  • 优势:体积极小(< 5MB),攻击面极小(无法在容器内执行 shell 命令)。
2. 缓存优化

利用 Docker 层缓存机制,将不常变的依赖下载放在COPY代码之前:

FROM maven:3.9 AS builder COPY pom.xml . RUN mvn dependency:go-offline -B # 这一层会被缓存,除非 pom.xml 变化 COPY src ./src RUN mvn package -DskipTests
3. 多架构构建

结合docker buildx构建多架构镜像:

dockerbuildx build--platformlinux/amd64,linux/arm64-tmyapp:latest--push.
4. 条件构建

根据构建参数选择不同阶段(较少用,但可行):

ARG BUILD_ENV=prod FROM node:18 AS dev # ... FROM node:18 AS prod # ... FROM ${BUILD_ENV} AS final # ...

五、对比总结

特性传统单阶段构建多阶段构建
镜像体积大(包含构建工具、源码)极小(仅含运行所需)
安全性低(包含 shell、编译器,易被攻击)(无多余工具,攻击面小)
构建速度较慢(每次全量构建)快(利用缓存层)
维护性低(Dockerfile 臃肿)高(逻辑清晰,阶段分离)
依赖管理混乱清晰(构建依赖与运行依赖分离)

六、最佳实践建议

  1. 始终使用多阶段构建:这是现代 Docker 构建的标准做法。
  2. 选择最小的运行镜像:优先使用alpineslimdistroless
  3. 静态链接:对于 Go、Rust 等语言,尽量编译静态二进制文件,避免依赖运行环境的库。
  4. 清理缓存:在构建阶段使用RUN ... && rm -rf ...清理临时文件(虽然多阶段构建通常不需要,但在单阶段中很重要)。
  5. 利用.dockerignore:排除不必要的文件(如.git,node_modules,*.log),减少构建上下文大小。
  6. 不要复制整个目录COPY --from=builder /app/dist /appCOPY --from=builder /app /app更安全,避免意外复制源码。
http://www.cnnetsun.cn/news/1592795.html

相关文章:

  • Qwen3-4B性能实测:在资源受限环境下的速度与质量平衡
  • Godep依赖自动发现机制:Go项目依赖管理的终极指南
  • 扩展开发指南:如何为pay-java-parent添加新的支付渠道
  • intv_ai_mk11实操手册:日志分析技巧——快速定位token截断/OOM/加载失败
  • Livebook会话管理终极指南:5个关键特性解析实时协作与状态同步
  • 别再只写服务端了!Spring Boot WebSocket 完整双端通信与自动重连保姆级教程
  • 像素剧本圣殿真实案例:独立游戏开发者用其72小时产出完整剧情文本
  • 飞拍实战:从拖影公式到曝光与速度的平衡艺术
  • S32K312 MCAL开发避坑指南:GPT/PIT定时器中断不触发?检查这5个配置细节
  • Nunchaku FLUX.1 CustomV3应用指南:打造专属二次元角色与场景
  • VMware Workstation 16开机自启踩坑实录:从环境变量报错到bat脚本优化,一篇搞定
  • 终极noice.nvim测试框架使用指南:编写和运行插件测试的完整教程
  • 树形DP题目
  • PyTorch数据预处理全流程:从计算mean/std到实现归一化与反归一化(附完整代码)
  • 视觉语言导航从入门到精通(二):核心模型架构与演进之路
  • Git-FTP 终极指南:如何用Git智能同步FTP部署的完整教程
  • 从零实现一个五子棋AI对手:详解Max-Min算法与Alpha-Beta剪枝在Flutter中的应用
  • 终极Leaf分布式优化指南:如何在多设备上高效训练神经网络
  • PHPBrew补丁机制终极指南:轻松解决特定环境编译问题
  • 避坑指南:ESP8266 wroom_02烧录AT固件时为什么总是卡在等待同步?
  • 【开题答辩全过程】以 基于微信小程序的蓝鲸旧物回收系统的设计与实现为例,包含答辩的问题和答案
  • Wan2.2-I2V-A14B混合云架构:私有核心+公有云弹性扩缩容视频生成方案
  • 别再盲目攻击了!用FIA的‘聚合梯度’思想,让你的对抗样本迁移成功率提升12%
  • DApp革命:当代码成为规则,你的数字人生谁主沉浮?
  • Benchmark.js性能测试数据持久化:完整指南教你保存和比较不同版本性能数据 [特殊字符]
  • Qwen1.5-0.5B-Chat实战部署:Docker容器化改造方案
  • Seed-Coder-8B-Base作品展示:AI生成的代码片段,质量堪比资深程序员
  • Fay框架API版本迁移工具:平滑升级方案
  • 【数据库 面试突击 · 03】大厂高频面试题:从存储过程到索引底层全解析
  • 通义千问3-4B实战:用Ollama三行命令搭建本地AI聊天机器人