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

Dockerfile系列(三) 多阶段构建-告别镜像obesity

多阶段构建:告别"镜像 Obesity"

本文基于 Docker 24.x + BuildKit,展示如何把 1GB+ 的镜像瘦身到 20MB。


场景引入:镜像胖到推不动

上篇咱们优化了构建速度,但构建完的镜像让我傻眼了:

$dockerimages|grepmy-app my-app latest abc1231.2GB

1.2GB!就一个简单的 Web 服务,里面塞了啥?

  • Node 基础镜像:~180MB
  • npm install的依赖:node_modules占了 800MB+
  • 构建工具(gcc、python、make):为了编译某些原生模块
  • 源码、测试文件、.git目录…

这就像你搬家时,把装修工具、建筑材料、设计图纸全塞进新房——能住人,但到处都是垃圾。

多阶段构建就是解决这个问题的大杀器。


核心原理:一个 Dockerfile,多个 “FROM”

传统 Dockerfile 只有一个FROM,构建产物和工具全塞一起。多阶段构建允许你写多个FROM,每个阶段是一个独立的构建环境,最后只把需要的产物拷贝到最终镜像。

类比:餐厅后厨 vs 前厅摆盘

想象你去高级餐厅吃饭:

  • 后厨(构建阶段):锅碗瓢盆、厨师、食材、调料,乱七八糟但功能齐全
  • 传菜口(COPY --from):只把做好的菜端出去
  • 前厅摆盘(最终镜像):精致的盘子、菜品,看不到后厨的油烟

多阶段构建就是这个逻辑:后厨多乱都没关系,客人(生产环境)只看到精致的成品。


实战对比:Go 应用的单阶段 vs 多阶段

Go 是最能体现多阶段构建价值的语言之一,因为编译后只有一个二进制文件。

单阶段:胖镜像(约 1GB)

FROM golang:1.21 WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o myapp . EXPOSE 8080 CMD ["./myapp"]
$dockerbuild-tmyapp-fat.$dockerimages|grepmyapp-fat myapp-fat latest1.05GB

多阶段:瘦镜像(约 15MB)

# ========== 阶段 1:编译 ========== FROM golang:1.21 AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . # 静态编译,不依赖系统库 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o myapp . # ========== 阶段 2:运行 ========== FROM alpine:latest # 安全考虑:用非 root 用户(下篇细讲) RUN adduser -D -u 1000 appuser USER appuser WORKDIR /app # 只拷贝编译好的二进制文件 COPY --from=builder /app/myapp . EXPOSE 8080 CMD ["./myapp"]
$dockerbuild-tmyapp-slim.$dockerimages|grepmyapp myapp-slim latest15.2MB myapp-fat latest1.05GB

从 1GB 到 15MB,瘦了 98%!而且最终镜像里没有 Go 编译器、没有源码、没有go.mod,只有一个能跑的二进制。


前端项目:Node 构建 → Nginx serving

前端项目同样适合多阶段,用 Node 构建,用 Nginx 托管静态文件:

# ========== 阶段 1:构建 ========== FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build # 生成 dist/ 目录 # ========== 阶段 2:托管 ========== FROM nginx:alpine # 只拷贝构建产物 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 Alpine(约 25MB),没有 Node、没有node_modules、没有源码。


Java 项目:Maven 构建 → JRE 运行

Java 也能大幅瘦身,从 JDK 切换到 JRE,甚至用自定义 JRE(Java 11+ 的jlink):

# ========== 阶段 1:编译 ========== FROM maven:3.9-eclipse-temurin-17-alpine AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline # 缓存依赖 COPY src ./src RUN mvn clean 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"]

JDK 镜像约 400MB,JRE 镜像约 150MB,省了 60%+。


进阶:极致瘦身——Distroless 和 Scratch

Distroless:Google 出品,极简但能用

FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 go build -o myapp . # 没有 shell、没有包管理器,只有运行必需的库 FROM gcr.io/distroless/static-debian12 COPY --from=builder /app/myapp / CMD ["/myapp"]

Distroless 镜像只有几十 MB,而且没有 shell,安全性极高(攻击者进去啥命令都执行不了)。

Scratch:从零开始

Go 可以编译成完全静态链接的二进制,连操作系统库都不依赖:

FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 go build -o myapp . # 空镜像,啥都没有 FROM scratch COPY --from=builder /app/myapp / # 需要拷贝 CA 证书才能发 HTTPS 请求 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ CMD ["/myapp"]

最终镜像:不到 10MB,这是 Docker 镜像的理论极限。

⚠️:Scratch 里没有sh,调试极其困难。生产环境用 Distroless 更平衡。


多阶段之间的数据传递

COPY --from是跨阶段拷贝的关键:

# 从指定阶段拷贝 COPY --from=builder /app/myapp . # 从指定镜像拷贝(甚至不需要前面定义过) COPY --from=nginx:alpine /etc/nginx/nginx.conf /tmp/ # 从索引号拷贝(第 0 个 FROM) COPY --from=0 /app/myapp .

一句话总结

多阶段构建就像餐厅后厨和前厅分离:编译工具、源码留在构建阶段,只把二进制产物带到最终镜像——体积能从 GB 压到 MB,攻击面还小了一大圈。


http://www.jsqmd.com/news/705270/

相关文章:

  • E-Hentai漫画下载器终极教程:5步轻松批量下载完整漫画合集
  • ARM处理器预取与分支预测技术解析
  • Onekey:一键自动化获取Steam Depot清单的终极解决方案
  • 3步解锁:让任天堂控制器在Windows上重获新生的终极兼容方案
  • 天赐范式第23天:数理化炼金术公式效验器技术确权报告——原数学毒丸公式效验器升级
  • 小型语言模型(SLM)实战:高效部署与成本优化指南
  • 《Windows Internals》10.2.14 学习笔记:网络驱动器盘符通知——为什么盘符变了,系统和应用必须“知道”?
  • 线性代数在机器学习中的应用与学习资源指南
  • 2026年如何部署Hermes Agent/OpenClaw?萌新部署及token Plan配置解析
  • 使用 VS code + Oracle java 插件搭建java语言原生的notebook环境
  • 3分钟搞定OFD转PDF:免费开源神器Ofd2Pdf使用全攻略
  • [SWPUCTF 2021 新生赛]gift_F12 WP
  • Web3数据基础设施Mega:模块化架构与实战部署指南
  • AIHawk:基于Python与GPT的自动化求职智能体开发实践
  • JoyCon-Driver:让Switch手柄在Windows上重获新生的终极方案
  • Java String增删改查操作详解
  • 终极指南:用RimSort彻底解决环世界MOD管理难题,告别游戏崩溃
  • OpenClaw vs Hermes Agent
  • 2026湖南企业获客新机遇:GEO正在取代SEO,AI问答已成主战场 - 星城方舟
  • 【评测系列4】测试视角:我通宵测了 ChatGPT Image 2:100%通过背后,藏着1个危险信号
  • ITK-SNAP医学图像分割:从入门到精通的完整操作指南
  • VAC-Bypass-Loader技术实现深度解析:Windows进程注入与反作弊绕过机制
  • 【MCP 2026低代码集成权威指南】:20年架构师亲授5步落地法,错过再等三年!
  • 23岁业余爱好者借助ChatGPT攻克60年未解数学难题,新方法或有广泛应用
  • 上海永辉超市卡回收指南 - 京顺回收
  • Arm Total Compute时钟控制架构与低功耗设计解析
  • XGBoost数据预处理实战:类别编码与缺失值处理
  • 风控误杀为什么总压不下来?从样本回溯、规则调优到效果评估一次讲透
  • WASM边缘服务上线倒计时:Docker Compose v2.22起支持wasm32-wasi,但92%开发者还没启用这个flag
  • FinAgent-从多数据源分析、Agent 编排到 Debate / Memory / Reflection 的工程化落地(二)