找回密码
 立即注册

QQ登录

只需一步,快速开始

peUfSYR.png
查看: 30|回复: 0

Day 10 多阶段构建:让你的镜像体积减少90%

[复制链接]

876

主题

13

回帖

2808

积分

管理员

积分
2808
发表于 2026-3-29 16:16:42 | 显示全部楼层 |阅读模式
🎭 多阶段构建:让你的镜像体积减少90%
Docker 30天实战系列 · Day 10

你是否遇到过这样的问题:

  • 📦 "镜像怎么这么大?" —— 一个简单的 Go 程序,镜像却有 1GB
  • 🐌 "部署太慢了" —— 镜像拉取要好几分钟
  • 💸 "存储费用太高" —— 镜像仓库空间告急

    今天,我们将学习 Docker 多阶段构建(Multi-stage Build),这是优化镜像体积的终极武器

    ━━━━━━━━━━━━━━━━━━━━

    本文你将学到

  • ✅ 理解多阶段构建的原理和优势
  • ✅ 掌握多阶段构建的语法和技巧
  • ✅ 实战:将 1.2GB 镜像优化到 15MB
  • ✅ 学会不同语言的多阶段构建最佳实践

    阅读时间:约 15 分钟
    实操时间:约 25 分钟
    难度等级:⭐⭐⭐⭐☆

    ━━━━━━━━━━━━━━━━━━━━

    前置准备

    | 项目 | 要求 |
    |------|------|
    | Docker | 20.10+ |
    | 前置知识 | Day 8-9 Dockerfile 基础 |
    1. # 创建工作目录
    2. mkdir -p ~/docker-practice/day10
    3. cd ~/docker-practice/day10
    复制代码

    ━━━━━━━━━━━━━━━━━━━━

    为什么需要多阶段构建?

    传统构建的问题

    以一个 Go 应用为例,传统 Dockerfile:
    1. # 传统方式:单阶段构建
    2. FROM golang:1.21
    3. WORKDIR /app
    4. COPY . .
    5. RUN go build -o main .
    6. CMD ["./main"]
    复制代码

    问题
  • 基础镜像
    1. golang:1.21
    复制代码
    约 800MB
  • 包含编译器、工具链等运行时不需要的东西
  • 最终镜像可能超过 1GB
    1. # 构建后查看大小
    2. docker images myapp
    3. # REPOSITORY   TAG       SIZE
    4. # myapp        latest    1.2GB  ← 太大了!
    复制代码

    多阶段构建的思路
    1. ┌─────────────────────────────────────────────────────────┐
    2. │                    多阶段构建原理                        │
    3. ├─────────────────────────────────────────────────────────┤
    4. │                                                         │
    5. │  阶段1:构建阶段 (Builder)                               │
    6. │  ┌─────────────────────────────────┐                   │
    7. │  │  FROM golang:1.21               │  ← 包含编译工具    │
    8. │  │  编译代码 → 生成可执行文件        │                   │
    9. │  └─────────────────────────────────┘                   │
    10. │                    │                                    │
    11. │                    │ 只复制编译产物                      │
    12. │                    ▼                                    │
    13. │  阶段2:运行阶段 (Runtime)                               │
    14. │  ┌─────────────────────────────────┐                   │
    15. │  │  FROM alpine:3.18               │  ← 最小运行环境    │
    16. │  │  只包含可执行文件                 │                   │
    17. │  └─────────────────────────────────┘                   │
    18. │                                                         │
    19. │  最终镜像:只有阶段2的内容!                              │
    20. └─────────────────────────────────────────────────────────┘
    复制代码

    ━━━━━━━━━━━━━━━━━━━━

    多阶段构建语法

    基本语法
    1. # 阶段1:构建阶段(命名为 builder)
    2. FROM golang:1.21 AS builder
    3. WORKDIR /app
    4. COPY . .
    5. RUN go build -o main .
    6. # 阶段2:运行阶段
    7. FROM alpine:3.18
    8. WORKDIR /app
    9. # 从 builder 阶段复制编译产物
    10. COPY --from=builder /app/main .
    11. CMD ["./main"]
    复制代码

    关键语法
    1. AS builder
    复制代码
    :给阶段命名
    1. COPY --from=builder
    复制代码
    :从指定阶段复制文件

    ━━━━━━━━━━━━━━━━━━━━

    实战一:Go 应用多阶段构建

    步骤 1:创建示例应用
    1. # 创建 main.go
    2. cat > main.go << 'EOF'
    3. package main
    4. import (
    5.     "fmt"
    6.     "net/http"
    7. )
    8. func main() {
    9.     http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    10.         fmt.Fprintf(w, "Hello from Docker Multi-stage Build!")
    11.     })
    12.     fmt.Println("Server starting on :8080...")
    13.     http.ListenAndServe(":8080", nil)
    14. }
    15. EOF
    16. # 创建 go.mod
    17. cat > go.mod << 'EOF'
    18. module myapp
    19. go 1.21
    20. EOF
    复制代码

    步骤 2:创建多阶段 Dockerfile
    1. cat > Dockerfile << 'EOF'
    2. # ============ 阶段1:构建 ============
    3. FROM golang:1.21-alpine AS builder
    4. # 设置工作目录
    5. WORKDIR /app
    6. # 复制依赖文件
    7. COPY go.mod ./
    8. # 下载依赖(如果有)
    9. RUN go mod download
    10. # 复制源码
    11. COPY . .
    12. # 编译(静态链接,禁用CGO)
    13. RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o main .
    14. # ============ 阶段2:运行 ============
    15. FROM alpine:3.18
    16. # 安装 CA 证书(HTTPS 请求需要)
    17. RUN apk --no-cache add ca-certificates
    18. WORKDIR /app
    19. # 从构建阶段复制可执行文件
    20. COPY --from=builder /app/main .
    21. # 暴露端口
    22. EXPOSE 8080
    23. # 运行
    24. CMD ["./main"]
    25. EOF
    复制代码

    步骤 3:构建并对比
    1. # 构建多阶段镜像
    2. docker build -t myapp:multistage .
    3. # 查看镜像大小
    4. docker images myapp
    复制代码

    对比结果

    | 构建方式 | 镜像大小 | 减少比例 |
    |----------|----------|----------|
    | 单阶段 (golang:1.21) | ~1.2GB | - |
    | 多阶段 (alpine) | ~15MB | 98.7% |

    步骤 4:验证运行
    1. # 运行容器
    2. docker run -d -p 8080:8080 --name myapp myapp:multistage
    3. # 测试
    4. curl http://localhost:8080
    5. # Hello from Docker Multi-stage Build!
    6. # 清理
    7. docker rm -f myapp
    复制代码

    ━━━━━━━━━━━━━━━━━━━━

    实战二:Node.js 应用多阶段构建

    Node.js 应用的多阶段构建稍有不同,因为需要保留 node_modules。

    创建示例应用
    1. mkdir -p nodejs-app && cd nodejs-app
    2. # package.json
    3. cat > package.json << 'EOF'
    4. {
    5.   "name": "nodejs-app",
    6.   "version": "1.0.0",
    7.   "main": "app.js",
    8.   "scripts": {
    9.     "start": "node app.js"
    10.   },
    11.   "dependencies": {
    12.     "express": "^4.18.2"
    13.   }
    14. }
    15. EOF
    16. # app.js
    17. cat > app.js << 'EOF'
    18. const express = require('express');
    19. const app = express();
    20. app.get('/', (req, res) => {
    21.   res.json({ message: 'Hello from Node.js Multi-stage Build!' });
    22. });
    23. app.listen(3000, () => {
    24.   console.log('Server running on port 3000');
    25. });
    26. EOF
    复制代码

    多阶段 Dockerfile
    1. # ============ 阶段1:安装依赖 ============
    2. FROM node:20-alpine AS deps
    3. WORKDIR /app
    4. COPY package*.json ./
    5. # 只安装生产依赖
    6. RUN npm ci --only=production
    7. # ============ 阶段2:构建(如果有构建步骤)============
    8. FROM node:20-alpine AS builder
    9. WORKDIR /app
    10. COPY package*.json ./
    11. RUN npm ci
    12. COPY . .
    13. # RUN npm run build  # 如果有构建步骤
    14. # ============ 阶段3:运行 ============
    15. FROM node:20-alpine AS runner
    16. WORKDIR /app
    17. # 创建非 root 用户
    18. RUN addgroup --system --gid 1001 nodejs
    19. RUN adduser --system --uid 1001 nodeuser
    20. # 从 deps 阶段复制生产依赖
    21. COPY --from=deps /app/node_modules ./node_modules
    22. COPY --from=builder /app/app.js ./
    23. COPY --from=builder /app/package.json ./
    24. USER nodeuser
    25. EXPOSE 3000
    26. CMD ["node", "app.js"]
    复制代码

    对比结果

    | 构建方式 | 镜像大小 |
    |----------|----------|
    | 单阶段 (node:20) | ~1.1GB |
    | 多阶段 (node:20-alpine) | ~180MB |

    ━━━━━━━━━━━━━━━━━━━━

    实战三:前端应用多阶段构建

    React/Vue 等前端应用的构建模式:
    1. # ============ 阶段1:构建 ============
    2. FROM node:20-alpine AS builder
    3. WORKDIR /app
    4. # 安装依赖
    5. COPY package*.json ./
    6. RUN npm ci
    7. # 构建
    8. COPY . .
    9. RUN npm run build
    10. # ============ 阶段2:Nginx 服务 ============
    11. FROM nginx:alpine
    12. # 复制构建产物到 Nginx
    13. COPY --from=builder /app/dist /usr/share/nginx/html
    14. # 复制自定义 Nginx 配置(可选)
    15. # COPY nginx.conf /etc/nginx/nginx.conf
    16. EXPOSE 80
    17. CMD ["nginx", "-g", "daemon off;"]
    复制代码

    对比结果

    | 构建方式 | 镜像大小 |
    |----------|----------|
    | 包含 node_modules | ~1.5GB |
    | 多阶段 (nginx:alpine) | ~25MB |

    ━━━━━━━━━━━━━━━━━━━━

    高级技巧

    技巧 1:使用 scratch 镜像(最小化)

    对于静态编译的 Go 程序:
    1. FROM golang:1.21-alpine AS builder
    2. WORKDIR /app
    3. COPY . .
    4. RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o main .
    5. # scratch 是空镜像,只有 0 字节
    6. FROM scratch
    7. COPY --from=builder /app/main /main
    8. ENTRYPOINT ["/main"]
    复制代码

    结果:镜像只有几 MB!

    技巧 2:从外部镜像复制
    1. # 从官方镜像复制二进制文件
    2. COPY --from=nginx:alpine /usr/sbin/nginx /usr/sbin/nginx
    复制代码

    技巧 3:多个构建目标
    1. FROM node:20-alpine AS base
    2. WORKDIR /app
    3. COPY package*.json ./
    4. # 开发阶段
    5. FROM base AS development
    6. RUN npm install
    7. COPY . .
    8. CMD ["npm", "run", "dev"]
    9. # 生产阶段
    10. FROM base AS production
    11. RUN npm ci --only=production
    12. COPY . .
    13. CMD ["npm", "start"]
    复制代码

    构建指定阶段:
    1. # 构建开发镜像
    2. docker build --target development -t myapp:dev .
    3. # 构建生产镜像
    4. docker build --target production -t myapp:prod .
    复制代码

    技巧 4:使用 BuildKit 缓存
    1. # syntax=docker/dockerfile:1.4
    2. FROM golang:1.21-alpine AS builder
    3. WORKDIR /app
    4. # 使用缓存挂载加速依赖下载
    5. RUN --mount=type=cache,target=/go/pkg/mod \
    6.     --mount=type=cache,target=/root/.cache/go-build \
    7.     go build -o main .
    复制代码

    ━━━━━━━━━━━━━━━━━━━━

    优化对比总结

    | 语言/框架 | 单阶段 | 多阶段 | 减少 |
    |-----------|--------|--------|------|
    | Go | 1.2GB | 15MB | 98% |
    | Node.js | 1.1GB | 180MB | 84% |
    | React | 1.5GB | 25MB | 98% |
    | Java (Spring) | 700MB | 200MB | 71% |
    | Python | 1GB | 150MB | 85% |

    ━━━━━━━━━━━━━━━━━━━━

    🤔 常见问题

    Q1:多阶段构建会增加构建时间吗?

    A:首次构建可能稍慢,但由于层缓存,后续构建通常更快。而且部署时间大幅减少。

    Q2:如何调试多阶段构建?

    A
    1. # 只构建到指定阶段
    2. docker build --target builder -t myapp:debug .
    3. # 进入容器调试
    4. docker run -it myapp:debug sh
    复制代码

    Q3:COPY --from 可以用阶段索引吗?

    A:可以,但不推荐:
    1. # 用索引(不推荐)
    2. COPY --from=0 /app/main .
    3. # 用名称(推荐)
    4. COPY --from=builder /app/main .
    复制代码

    ━━━━━━━━━━━━━━━━━━━━

    📚 本文总结

    核心要点

  • 多阶段构建原理
  • 使用多个 FROM 指令
  • 只保留最后阶段的内容
  • 通过 COPY --from 传递产物

  • 关键语法
    1.    FROM image AS name
    2.    COPY --from=name /src /dest
    复制代码

  • 最佳实践
  • 构建阶段用完整镜像
  • 运行阶段用最小镜像(alpine/scratch)
  • 静态编译消除运行时依赖

  • 适用场景
  • 编译型语言(Go、Rust、C++)
  • 需要构建的前端应用
  • 任何需要优化镜像大小的场景

    ━━━━━━━━━━━━━━━━━━━━

    下一步

    明天我们将学习:Day 11 - 基础镜像选择指南:Alpine vs Ubuntu vs Distroless

    你将了解不同基础镜像的特点,学会为项目选择最合适的基础镜像。

    ━━━━━━━━━━━━━━━━━━━━

    💬 互动时间

    今日作业
  • 用多阶段构建优化你的一个项目
  • 对比优化前后的镜像大小
  • 尝试使用 scratch 镜像

    在评论区分享:
  • 你的镜像优化了多少?
  • 遇到了什么问题?

    ━━━━━━━━━━━━━━━━━━━━

    🐳 加入 Docker 学习群

    扫码加入 Docker 学习交流群,和大家一起讨论实践:



    🔔 关注公众号,不错过每一篇干货!
  • 您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    扫码关注微信公众号

    Archiver|手机版|小黑屋|风叶林

    GMT+8, 2026-5-8 23:02 , Processed in 0.146625 second(s), 20 queries .

    Powered by 风叶林

    © 2001-2026 Discuz! Team.

    快速回复 返回顶部 返回列表