Docker 多阶段构建:镜像体积减少 90% 的技巧
Docker 多阶段构建:镜像体积减少 90% 的技巧
引言:Docker 镜像体积的噩梦
想象一下,一个 2GB 的 Docker 镜像,传输需要 30 分钟,启动需要 2 分钟,存储占用巨大…
这就是没有使用多阶段构建的 Docker 镜像的真实写照。今天这篇教程将带你彻底掌握多阶段构建,让你的镜像体积减少 90%!
第一章:为什么需要多阶段构建?
1.1 传统 Docker 镜像的问题
“`dockerfile
❌ 传统 Dockerfile 的问题
FROM golang:1.21
安装依赖
RUN apt-get update && apt-get install -y \
git \
curl \
build-essential
克隆源码
RUN git clone https://github.com/yourapp/yourapp.git
编译应用
WORKDIR /yourapp
RUN go build -o main .
复制源码
COPY . .
运行时环境
RUN apt-get install -y \
openssl \
ca-certificates
结果:镜像体积 1.5GB
其中包含:编译器、依赖、源码、构建工具
传统镜像的问题:
-
- 体积巨大(1-2GB)
-
- 包含不必要的构建工具
-
- 镜像传输慢
-
- 启动时间长
-
- 安全风险高
1.2 多阶段构建的优势
dockerfile
✅ 多阶段构建
第一阶段:构建
FROM golang:1.21 AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
第二阶段:运行
FROM alpine:3.18
RUN apk –no-cache add ca-certificates
WORKDIR /root/
COPY –from=builder /build/main .
EXPOSE 8080
CMD [“./main”]
结果:镜像体积 35MB
相比传统方式减少 97.5%!
多阶段构建的优势:
-
- 镜像体积减少 90%+
-
- 只包含运行时依赖
-
- 更安全的镜像
-
- 更快的构建和部署
-
- 更好的缓存机制
第二章:多阶段构建原理
2.1 工作原理
┌─────────────────────────────────────────────────┐
│ 多阶段构建流程 │
├─────────────────────────────────────────────────┤
│ │
│ Dockerfile: │
│ FROM base1 # 阶段 1 │
│ …build steps… │
│ FROM base2 AS final # 阶段 2 │
│ COPY –from=builder /output . │
│ │
│ 构建过程: │
│ 1. 构建阶段 1 → 生成产物 │
│ 2. 构建阶段 2 → 复制产物 → 生成最终镜像 │
│ 3. 只保留最终阶段 │
│ │
│ 优势: │
│ ✓ 构建工具不进入最终镜像 │
│ ✓ 多个阶段独立构建 │
│ ✓ 灵活复用中间产物 │
└─────────────────────────────────────────────────┘
2.2 构建缓存优化
dockerfile
✅ 优化缓存顺序
FROM golang:1.21 AS builder
1. 先复制依赖文件(变化少)
COPY go.mod go.sum ./
RUN go mod download
2. 再复制源码(变化多)
COPY . .
3. 最后编译
RUN go build -o main .
这样修改代码时不会重新下载依赖!
第三章:实战案例
3.1 Go 应用多阶段构建
dockerfile
Go 应用的完整多阶段构建
FROM golang:1.21-alpine AS builder
设置工作目录
WORKDIR /build
复制依赖文件
COPY go.mod go.sum ./
RUN go mod download
安装构建工具(仅构建阶段)
RUN apk add –no-cache git
克隆源码
RUN git clone https://github.com/yourorg/yourapp.git .
编译应用
CGO_ENABLED=0 禁用 Cgo,生成纯二进制
GOOS=linux 编译 Linux 版本
GOARCH=amd64 指定架构
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags=”-s -w” -o main .
验证二进制文件
RUN ls -lh main
生产阶段
FROM alpine:3.18
创建非 root 用户
RUN adduser -D -g ” appuser
安装运行时依赖
RUN apk –no-cache add ca-certificates tzdata
设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
复制构建产物
COPY –from=builder /build/main /app/main
COPY –from=builder /build/config.yaml /app/config.yaml
设置权限
RUN chown -R appuser:appuser /app
切换到非 root 用户
USER appuser
WORKDIR /app
暴露端口
EXPOSE 8080
健康检查
HEALTHCHECK –interval=30s –timeout=3s –start-period=5s –retries=3 \
CMD wget –no-verbose –tries=1 –spider http://localhost:8080/health || exit 1
启动应用
CMD [“./main”]
可选:添加入口脚本
ENTRYPOINT [“/app/entrypoint.sh”]
镜像大小对比:
传统方式:
- 镜像:1.2GB
- 层数:25 层
- 构建时间:5 分钟
多阶段构建:
- 镜像:35MB
- 层数:8 层
- 构建时间:5 分钟(相同)
节省:97% 体积
3.2 Node.js 应用多阶段构建
dockerfile
Node.js 应用多阶段构建
第一阶段:构建
FROM node:20-alpine AS builder
WORKDIR /app
复制 package.json
COPY package*.json ./
安装依赖(使用国内镜像加速)
RUN npm config set registry https://registry.npmmirror.com && \
npm ci –only=production
复制源码
COPY . .
构建应用
RUN npm run build
生产阶段
FROM node:20-alpine
安装运行时依赖
RUN apk –no-cache add \
ca-certificates \
&& rm -rf /var/cache/apk/*
WORKDIR /app
复制 node_modules
COPY –from=builder –chown=node:node /app/node_modules ./node_modules
复制构建产物
COPY –from=builder –chown=node:node /app/dist ./dist
COPY –from=builder –chown=node:node /app/package.json ./
切换到非 root 用户
USER node
EXPOSE 3000
环境变量
ENV NODE_ENV=production
ENV PORT=3000
CMD [“node”, “dist/index.js”]
镜像大小对比:
传统方式:
- 镜像:950MB
- 包含:Node.js + npm + 构建工具
多阶段构建:
- 镜像:85MB
- 包含:Node.js + 生产依赖
节省:91% 体积
3.3 Rust 应用多阶段构建
dockerfile
Rust 应用多阶段构建
第一阶段:构建
FROM rust:1.75 AS builder
WORKDIR /build
创建项目结构
RUN cargo new myapp –bin
WORKDIR /build/myapp
复制 Cargo.toml
COPY Cargo.toml ./
下载依赖
RUN cargo fetch
复制源码
COPY src ./src
发布构建(优化版本)
RUN cargo build –release
验证二进制文件
RUN ls -lh target/release/myapp
生产阶段
FROM gcr.io/distroless/cc-debian12
WORKDIR /app
复制编译好的二进制文件
COPY –from=builder /build/myapp/target/release/myapp /app/myapp
设置入口点
ENTRYPOINT [“/app/myapp”]
镜像大小对比:
传统方式:
- 镜像:400MB
- 包含:Rust 工具链
多阶段构建:
- 镜像:15MB
- 包含:仅二进制文件
节省:96% 体积
3.4 Python 应用多阶段构建
dockerfile
Python 应用多阶段构建
第一阶段:构建和测试
FROM python:3.11-slim AS builder
WORKDIR /app
安装构建依赖
RUN apt-get update && apt-get install -y \
gcc \
python3-dev \
&& rm -rf /var/lib/apt/lists/*
复制依赖文件
COPY requirements.txt .
RUN pip install –user –no-cache-dir -r requirements.txt
复制源码
COPY . .
运行测试
RUN pytest tests/ || echo “Tests failed but continuing…”
第二阶段:运行
FROM python:3.11-slim AS runner
WORKDIR /app
安装运行时依赖
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
复制虚拟环境
COPY –from=builder /root/.local /root/.local
设置 PATH
ENV PATH=/root/.local/bin:$PATH
复制源码
COPY . .
创建非 root 用户
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
环境变量
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
EXPOSE 8000
CMD [“python”, “app.py”]
镜像大小对比:
传统方式:
- 镜像:800MB
- 包含:完整 Python + 构建工具
多阶段构建:
- 镜像:120MB
- 包含:Python + 依赖
节省:85% 体积
3.5 Java 应用多阶段构建
dockerfile
Java 应用多阶段构建(Maven)
第一阶段:构建
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
复制 pom.xml
COPY pom.xml .
下载依赖(缓存优化)
RUN mvn dependency:go-offline -B
复制源码
COPY src ./src
构建应用(跳过测试加速)
RUN mvn clean package -DskipTests -B
第二阶段:运行
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
复制构建产物
COPY –from=builder /app/target/myapp-1.0.jar app.jar
设置权限
RUN chown -R appuser:appgroup /app
USER appuser
JVM 参数优化
ENV JAVA_OPTS=”-Xms256m -Xmx512m -XX:+UseG1GC”
健康检查
HEALTHCHECK –interval=30s –timeout=3s –retries=3 \
CMD wget –quiet –tries=1 –spider http://localhost:8080/health || exit 1
启动应用
ENTRYPOINT [“sh”, “-c”, “java $JAVA_OPTS -jar app.jar”]
镜像大小对比:
传统方式:
- 镜像:600MB
- 包含:JDK + Maven + 依赖
多阶段构建:
- 镜像:180MB
- 包含:JRE + 应用
节省:70% 体积
第四章:镜像优化技巧
4.1 多阶段构建优化策略
dockerfile
✅ 优化 1:最小化基础镜像
使用 -alpine 或 -slim 版本
FROM golang:1.21-alpine # 而不是 golang:1.21
✅ 优化 2:删除不必要的文件
FROM golang:1.21 AS builder
RUN rm -rf /root/.cache/go-build
✅ 优化 3:使用 .dockerignore
.dockerignore 文件
.git
*.md
tests/
*.log
.env
✅ 优化 4:并行构建
FROM golang:1.21 AS builder
RUN go build -j=4 -o main .
✅ 优化 5:多架构支持
使用 buildx 多平台构建
docker buildx build –platform linux/amd64,linux/arm64 -t myapp:latest .
4.2 镜像层优化
dockerfile
❌ 错误的层优化
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
✅ 正确的层优化
RUN apt-get update && apt-get install -y \
git \
curl \
&& rm -rf /var/lib/apt/lists/*
层优化效果:
错误方式:
- 镜像层:10 层
- 体积:200MB
正确方式:
- 镜像层:3 层
- 体积:180MB
节省:10% 体积
4.3 压缩和优化
dockerfile
✅ 使用压缩优化镜像
FROM alpine:3.18 AS runner
使用 gzip 压缩配置
COPY –from=builder config.json.gz /app/config.json.gz
RUN gunzip /app/config.json.gz
✅ 使用 distroless 基础镜像
FROM gcr.io/distroless/static-debian12
仅包含二进制文件,无 shell、无包管理器
COPY –from=builder /build/myapp /app/myapp
CMD [“/app/myapp”]
distroless 镜像对比:
传统方式:
- 镜像:100MB
- 包含:shell、bash、包管理器等
distroless:
- 镜像:12MB
- 仅包含二进制和必要库
节省:88% 体积
第五章:性能优化
5.1 构建时间优化
dockerfile
✅ 优化 1:缓存依赖
FROM golang:1.21-alpine AS builder
先下载依赖(缓存友好)
COPY go.mod go.sum ./
RUN go mod download
再复制源码(变化频繁)
COPY . .
RUN go build -o main .
5.2 并行构建
dockerfile
✅ 并行构建优化
FROM golang:1.21-alpine AS builder
并行编译
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags=”-s -w” \
-trimpath \
-gcflags=”all=-N -l” \
-o main .
5.3 镜像扫描
bash
优化后镜像扫描
docker scan myapp:latest
使用 trivy 扫描
trivy image myapp:latest
使用 grype 扫描
grype myapp:latest -o sarif > results.sarif
5.4 完整优化示例
dockerfile
最终优化版本
FROM golang:1.21-alpine AS builder
WORKDIR /build
1. 缓存依赖
COPY go.mod go.sum ./
RUN go mod download
2. 复制源码
COPY . .
3. 编译优化
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags=”-s -w -X main.Version=1.0.0″ \
-trimpath -o main .
4. 验证
RUN ./main version
生产阶段
FROM alpine:3.18
安装运行时依赖
RUN apk –no-cache add ca-certificates tzdata
创建用户
RUN adduser -D -g ” appuser
WORKDIR /app
COPY –from=builder /build/main .
COPY –from=builder /build/config.yaml .
RUN chown -R appuser:appuser /app
USER appuser
ENV TZ=Asia/Shanghai
EXPOSE 8080
HEALTHCHECK –interval=30s –timeout=3s –retries=3 \
CMD wget –no-verbose –tries=1 –spider http://localhost:8080/health || exit 1
CMD [“./main”]
第六章:最佳实践
6.1 最佳实践清单
✅ 最佳实践:
-
- 始终使用多阶段构建
- 使用最小化基础镜像(-alpine, -slim)
- 优化 Dockerfile 层顺序
- 使用 .dockerignore
- 删除构建产物中的临时文件
- 使用非 root 用户
- 添加健康检查
- 使用 .dockerignore 排除不必要文件
- 扫描镜像漏洞
- 使用标签管理版本
❌ 避免:
-
-
- 在 Dockerfile 中下载大文件
- 在镜像中安装开发工具
- 使用 root 用户运行
- 不设置资源限制
- 忘记删除缓存文件
- 不使用 .dockerignore
-
6.2 性能对比数据
┌─────────────────────────────────┬──────────────┬──────────────┬────────────┐
│ 场景 │ 传统方式 │ 多阶段构建 │ 优化 │
├─────────────────────────────────┼──────────────┼──────────────┼────────────┤
│ Go 应用(100 个依赖) │ 1.2GB │ 35MB │ 97%↓ │
│ Node.js 应用(200 个依赖) │ 950MB │ 85MB │ 91%↓ │
│ Python 应用(50 个依赖) │ 800MB │ 120MB │ 85%↓ │
│ Rust 应用 │ 400MB │ 15MB │ 96%↓ │
│ Java 应用(Spring Boot) │ 600MB │ 180MB │ 70%↓ │
│ 构建时间 │ 5 分钟 │ 5 分钟 │ 持平 │
│ 镜像推送时间(1G) │ 60 秒 │ 2 秒 │ 97%↓ │
│ 容器启动时间 │ 30 秒 │ 5 秒 │ 83%↓ │
└─────────────────────────────────┴──────────────┴──────────────┴────────────┘
6.3 监控指标
bash
镜像大小监控
docker images –format “table {{.Repository}}\t{{.Tag}}\t{{.Size}}”
镜像层分析
docker history myapp:latest
容器资源使用
docker stats –no-stream
构建时间监控
time docker build -t myapp:latest .
“`
总结:多阶段构建最佳实践
通过多阶段构建:
核心优势:
-
-
-
- 镜像体积减少 70-97%
- 更安全的镜像
- 更快的部署速度
- 更好的缓存利用
-
-
最佳实践:
-
-
-
- ✅ 始终使用多阶段构建
- ✅ 使用最小化基础镜像
- ✅ 优化 Dockerfile 层顺序
- ✅ 使用非 root 用户
- ✅ 添加健康检查
-
-
性能提升:
-
-
-
- 构建时间:持平或更快
- 部署时间:减少 97%
- 存储成本:降低 90%
- 传输时间:减少 97%
-
-
掌握多阶段构建,让你的 Docker 镜像更小、更快、更安全!🚀
—
参考资源:
-
-
- [Docker 官方文档 – 多阶段构建](https://docs.docker.com/build/building/multi-stage/)
- [最佳实践指南](https://docs.docker.com/develop/develop-images/docker-best-practices/)
- [Dockerfile 参考](https://docs.docker.com/engine/reference/builder/)
- [Alpine 镜像](https://alpinelinux.org/)
-




发表评论