Appearance
多阶段构建
多阶段构建(Multi-stage Build)是 Docker 17.05+ 引入的功能,允许在一个 Dockerfile 中使用多个 FROM 指令,每个 FROM 都是一个独立的构建阶段。最终镜像只包含最后阶段的内容,大幅减小镜像体积。
为什么需要多阶段构建
传统方式的问题
开发依赖 编译工具 源代码 测试文件
+ + + +
最终镜像(体积庞大)传统做法的痛点:
- 编译工具(gcc、maven、node 等)包含在最终镜像中,体积大
- 源代码、测试文件等不必要内容暴露在镜像中
- 安全面更大
多阶段构建方案
阶段一(构建):包含所有构建工具 → 编译出产物
阶段二(运行):只包含运行时 + 编译产物 → 最终镜像Go 应用示例
Go 编译后是静态二进制,最终镜像可以从 scratch 开始:
dockerfile
# ===== 阶段一:编译 =====
FROM golang:1.21-alpine AS builder
WORKDIR /build
# 先复制依赖文件(利用缓存)
COPY go.mod go.sum ./
RUN go mod download
# 复制源码并编译
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
# ===== 阶段二:运行 =====
FROM scratch AS runtime
# 从构建阶段复制二进制
COPY --from=builder /build/app /app
# 如果需要 HTTPS,还需要 CA 证书
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
CMD ["/app"]对比效果:
| 单阶段(golang:alpine) | 多阶段(scratch) | |
|---|---|---|
| 镜像大小 | ~600MB | ~8MB |
| 安全面 | 大 | 极小 |
Node.js 应用示例
dockerfile
# ===== 阶段一:安装依赖 =====
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# ===== 阶段二:构建 =====
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ===== 阶段三:运行 =====
FROM node:20-alpine AS runtime
ENV NODE_ENV=production
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# 从 deps 阶段复制生产依赖
COPY --from=deps --chown=appuser:appgroup /app/node_modules ./node_modules
# 从 builder 阶段复制构建产物
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --chown=appuser:appgroup package.json .
USER appuser
EXPOSE 3000
CMD ["node", "dist/main.js"]Java Maven 应用示例
dockerfile
# ===== 阶段一:构建 =====
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
# 先复制 pom.xml 下载依赖(利用缓存)
COPY pom.xml .
RUN mvn dependency:go-offline -B
# 复制源码并构建
COPY src ./src
RUN mvn package -DskipTests
# ===== 阶段二:运行 =====
FROM eclipse-temurin:17-jre-alpine AS runtime
RUN addgroup -S spring && adduser -S spring -G spring
WORKDIR /app
COPY --from=builder --chown=spring:spring /build/target/*.jar app.jar
USER spring
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]Python 应用示例
dockerfile
# ===== 阶段一:构建依赖 =====
FROM python:3.12-slim AS builder
WORKDIR /build
# 安装编译依赖
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# ===== 阶段二:运行 =====
FROM python:3.12-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
RUN addgroup --system appgroup && adduser --system --group appuser
WORKDIR /app
# 从构建阶段复制已安装的 Python 包
COPY --from=builder --chown=appuser:appgroup /root/.local /home/appuser/.local
COPY --chown=appuser:appgroup . .
USER appuser
ENV PATH=/home/appuser/.local/bin:$PATH
EXPOSE 5000
CMD ["python", "app.py"]阶段引用方式
按名称引用
dockerfile
FROM node:20 AS builder
# ...
FROM nginx:alpine AS runtime
COPY --from=builder /app/dist /usr/share/nginx/html按索引引用
dockerfile
FROM node:20 # 阶段 0
# ...
FROM nginx:alpine
COPY --from=0 /app/dist /usr/share/nginx/html # 引用第 0 阶段引用外部镜像
dockerfile
# 直接从任意镜像复制文件
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
COPY --from=golang:1.21 /usr/local/go /usr/local/go构建指定阶段
bash
# 只构建到指定阶段(用于调试)
docker build --target builder -t myapp-builder .
# 正常构建最终阶段
docker build -t myapp:latest .利用缓存加速构建
多阶段构建时合理安排指令顺序可以最大化缓存利用:
dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
# 先复制依赖文件 ← 变化频率低,缓存稳定
COPY package*.json ./
RUN npm ci
# 再复制源码 ← 变化频率高,修改代码不影响依赖缓存
COPY . .
RUN npm run build实际效果对比
bash
# 构建并查看镜像大小
docker build -t myapp-single -f Dockerfile.single .
docker build -t myapp-multi -f Dockerfile.multi .
docker images | grep myapp
# myapp-single latest xxx 600MB (含 node:20 完整环境)
# myapp-multi latest xxx 80MB (仅含运行时)总结
多阶段构建的核心价值:
- 减小镜像体积:最终镜像只包含运行时所需内容
- 提升安全性:构建工具、源码、测试代码不进入最终镜像
- 清晰的构建流程:构建和运行关注点分离
常见组合:
- Go:
golang→scratch或alpine - Node.js:
node构建 →node:alpine运行 - Java:
maven→jre-alpine - Python:
python:slim安装依赖 →python:slim运行
下一节介绍镜像构建的最佳实践。