冷蟊初退 孤灯野澜 志起鸡鸣 墓不悲秋 技术交流 软件开发 商业合作 加Q:411239339

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

浏览:9次阅读
没有评论

共计 4172 个字符,预计需要花费 11 分钟才能阅读完成。

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

Docker 30 天实战系列 · Day 10

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

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

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


本文你将学到

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

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


前置准备

项目 要求
Docker 20.10+
前置知识 Day 8-9 Dockerfile 基础
# 创建工作目录
mkdir -p ~/docker-practice/day10
cd ~/docker-practice/day10

为什么需要多阶段构建?

传统构建的问题

以一个 Go 应用为例,传统 Dockerfile:

# 传统方式:单阶段构建
FROM golang:1.21

WORKDIR /app
COPY . .
RUN go build -o main .

CMD ["./main"]

问题

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

多阶段构建的思路

┌─────────────────────────────────────────────────────────┐
│                    多阶段构建原理                        │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  阶段 1:构建阶段 (Builder)                               │
│  ┌─────────────────────────────────┐                   │
│  │  FROM golang:1.21               │  ← 包含编译工具    │
│  │  编译代码 → 生成可执行文件        │                   │
│  └─────────────────────────────────┘                   │
│                    │                                    │
│                    │ 只复制编译产物                      │
│                    ▼                                    │
│  阶段 2:运行阶段 (Runtime)                               │
│  ┌─────────────────────────────────┐                   │
│  │  FROM alpine:3.18               │  ← 最小运行环境    │
│  │  只包含可执行文件                 │                   │
│  └─────────────────────────────────┘                   │
│                                                         │
│  最终镜像:只有阶段 2 的内容!│
└─────────────────────────────────────────────────────────┘

多阶段构建语法

基本语法

# 阶段 1:构建阶段(命名为 builder)FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o main .

# 阶段 2:运行阶段
FROM alpine:3.18
WORKDIR /app
# 从 builder 阶段复制编译产物
COPY --from=builder /app/main .
CMD ["./main"]

关键语法

  • AS builder:给阶段命名
  • COPY --from=builder:从指定阶段复制文件

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

步骤 1:创建示例应用

# 创建 main.go
cat > main.go << 'EOF'
package main

import (
    "fmt"
    "net/http"
)

func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello from Docker Multi-stage Build!")
    })
    fmt.Println("Server starting on :8080...")
    http.ListenAndServe(":8080", nil)
}
EOF

# 创建 go.mod
cat > go.mod << 'EOF'
module myapp
go 1.21
EOF

步骤 2:创建多阶段 Dockerfile

cat > Dockerfile << 'EOF'
# ============ 阶段 1:构建 ============
FROM golang:1.21-alpine AS builder

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY go.mod ./

# 下载依赖(如果有)RUN go mod download

# 复制源码
COPY . .

# 编译(静态链接,禁用 CGO)RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o main .

# ============ 阶段 2:运行 ============
FROM alpine:3.18

# 安装 CA 证书(HTTPS 请求需要)RUN apk --no-cache add ca-certificates

WORKDIR /app

# 从构建阶段复制可执行文件
COPY --from=builder /app/main .

# 暴露端口
EXPOSE 8080

# 运行
CMD ["./main"]
EOF

步骤 3:构建并对比

# 构建多阶段镜像
docker build -t myapp:multistage .

# 查看镜像大小
docker images myapp

对比结果

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

步骤 4:验证运行

# 运行容器
docker run -d -p 8080:8080 --name myapp myapp:multistage

# 测试
curl http://localhost:8080
# Hello from Docker Multi-stage Build!

# 清理
docker rm -f myapp

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

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

创建示例应用

mkdir -p nodejs-app && cd nodejs-app

# package.json
cat > package.json << 'EOF'
{
  "name": "nodejs-app",
  "version": "1.0.0",
  "main": "app.js",
  "scripts": {"start": "node app.js"},
  "dependencies": {"express": "^4.18.2"}
}
EOF

# app.js
cat > app.js << 'EOF'
const express = require('express');
const app = express();

app.get('/', (req, res) => {res.json({ message: 'Hello from Node.js Multi-stage Build!'});
});

app.listen(3000, () => {console.log('Server running on port 3000');
});
EOF

多阶段 Dockerfile

# ============ 阶段 1:安装依赖 ============
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
# 只安装生产依赖
RUN npm ci --only=production

# ============ 阶段 2:构建(如果有构建步骤)============
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# RUN npm run build  # 如果有构建步骤

# ============ 阶段 3:运行 ============
FROM node:20-alpine AS runner
WORKDIR /app

# 创建非 root 用户
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nodeuser

# 从 deps 阶段复制生产依赖
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/app.js ./
COPY --from=builder /app/package.json ./

USER nodeuser
EXPOSE 3000
CMD ["node", "app.js"]

对比结果

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

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

React/Vue 等前端应用的构建模式:

# ============ 阶段 1:构建 ============
FROM node:20-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/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

对比结果

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

高级技巧

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

对于静态编译的 Go 程序:

FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o main .

# scratch 是空镜像,只有 0 字节
FROM scratch
COPY --from=builder /app/main /main
ENTRYPOINT ["/main"]

结果:镜像只有几 MB!

技巧 2:从外部镜像复制

# 从官方镜像复制二进制文件
COPY --from=nginx:alpine /usr/sbin/nginx /usr/sbin/nginx

技巧 3:多个构建目标

FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./

# 开发阶段
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

# 生产阶段
FROM base AS production
RUN npm ci --only=production
COPY . .
CMD ["npm", "start"]

构建指定阶段:

# 构建开发镜像
docker build --target development -t myapp:dev .

# 构建生产镜像
docker build --target production -t myapp:prod .

技巧 4:使用 BuildKit 缓存

# syntax=docker/dockerfile:1.4
FROM golang:1.21-alpine AS builder
WORKDIR /app

# 使用缓存挂载加速依赖下载
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    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

# 只构建到指定阶段
docker build --target builder -t myapp:debug .

# 进入容器调试
docker run -it myapp:debug sh

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

A:可以,但不推荐:

# 用索引(不推荐)COPY --from=0 /app/main .

# 用名称(推荐)COPY --from=builder /app/main .

📚 本文总结

核心要点

  1. 多阶段构建原理

    • 使用多个 FROM 指令
    • 只保留最后阶段的内容
    • 通过 COPY --from 传递产物
  2. 关键语法

    FROM image AS name
    COPY --from=name /src /dest
  3. 最佳实践

    • 构建阶段用完整镜像
    • 运行阶段用最小镜像(alpine/scratch)
    • 静态编译消除运行时依赖
  4. 适用场景

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

下一步

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

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


💬 互动时间

今日作业

  1. 用多阶段构建优化你的一个项目
  2. 对比优化前后的镜像大小
  3. 尝试使用 scratch 镜像

在评论区分享:

  • 你的镜像优化了多少?
  • 遇到了什么问题?

🐳 加入 Docker 学习群

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

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

🔔 关注公众号,不错过每一篇干货!

正文完
创作不易,扫码加点动力
post-qrcode
 0
果较瘦
版权声明:本站原创文章,由 果较瘦 于2026-03-29发表,共计4172字。
转载说明:除特殊说明外本站文章皆由果较瘦原创发布,转载请注明出处。