多阶段构建 #

什么是多阶段构建? #

多阶段构建允许在一个Dockerfile中使用多个FROM语句,每个FROM开始一个新的构建阶段,可以选择性地从之前的阶段复制文件。

传统构建问题 #

text
┌─────────────────────────────────────────────────────┐
│                 传统构建问题                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  问题:                                              │
│  1. 构建工具留在最终镜像中                          │
│  2. 镜像体积过大                                    │
│  3. 安全风险增加                                    │
│  4. 需要多个Dockerfile                              │
│                                                     │
│  示例:                                              │
│  FROM golang:1.20                                   │
│  WORKDIR /app                                       │
│  COPY . .                                           │
│  RUN go build -o myapp                              │
│  CMD ["./myapp"]                                    │
│                                                     │
│  结果: 镜像包含Go编译器,体积约1GB                  │
│                                                     │
└─────────────────────────────────────────────────────┘

多阶段构建优势 #

text
┌─────────────────────────────────────────────────────┐
│               多阶段构建优势                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  1. 镜像体积小 - 只包含运行时必需文件               │
│  2. 安全性高 - 不包含构建工具                       │
│  3. 单文件 - 一个Dockerfile完成所有构建             │
│  4. 可维护性好 - 构建逻辑清晰                       │
│                                                     │
└─────────────────────────────────────────────────────┘

基本语法 #

简单示例 #

dockerfile
# 构建阶段
FROM golang:1.20 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# 运行阶段
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app/myapp .
CMD ["./myapp"]

语法说明 #

dockerfile
# FROM ... AS <name>  命名构建阶段
FROM node:18 AS builder

# COPY --from=<stage> 从指定阶段复制文件
COPY --from=builder /app/dist /app/dist

# FROM --platform 指定平台
FROM --platform=linux/amd64 node:18 AS builder

实战示例 #

Go应用 #

dockerfile
# 构建阶段
FROM golang:1.20-alpine 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 main .

# 运行阶段
FROM alpine:3.18

# 安装CA证书
RUN apk --no-cache add ca-certificates tzdata

WORKDIR /root/

# 从构建阶段复制二进制文件
COPY --from=builder /app/main .

# 暴露端口
EXPOSE 8080

# 运行
CMD ["./main"]

Node.js应用 #

dockerfile
# 构建阶段
FROM node:18-alpine AS builder

WORKDIR /app

# 复制依赖文件
COPY package*.json ./
RUN npm ci

# 复制源代码
COPY . .

# 构建
RUN npm run build

# 运行阶段
FROM node:18-alpine

WORKDIR /app

# 复制依赖
COPY package*.json ./
RUN npm ci --only=production

# 从构建阶段复制构建产物
COPY --from=builder /app/dist ./dist

# 暴露端口
EXPOSE 3000

# 运行
CMD ["node", "dist/index.js"]

前端应用 #

dockerfile
# 构建阶段
FROM node:18-alpine AS builder

WORKDIR /app

# 复制依赖文件
COPY package*.json ./
RUN npm ci

# 复制源代码
COPY . .

# 构建
RUN npm run build

# 运行阶段
FROM nginx:alpine

# 从构建阶段复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html

# 复制nginx配置
COPY nginx.conf /etc/nginx/nginx.conf

# 暴露端口
EXPOSE 80

# 运行
CMD ["nginx", "-g", "daemon off;"]

Python应用 #

dockerfile
# 构建阶段
FROM python:3.11 AS builder

WORKDIR /app

# 安装依赖
COPY requirements.txt .
RUN pip install --user -r requirements.txt

# 运行阶段
FROM python:3.11-slim

WORKDIR /app

# 从构建阶段复制依赖
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH

# 复制应用代码
COPY . .

# 运行
CMD ["python", "app.py"]

高级技巧 #

多阶段选择 #

dockerfile
# 开发阶段
FROM node:18 AS development
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "run", "dev"]

# 构建阶段
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

# 生产阶段
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]

# 测试阶段
FROM node:18 AS test
WORKDIR /app
COPY . .
RUN npm ci
RUN npm test
bash
# 构建特定阶段
docker build --target development -t myapp:dev .
docker build --target production -t myapp:prod .

从外部镜像复制 #

dockerfile
# 从其他镜像复制文件
FROM alpine:3.18
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/nginx.conf

使用多阶段优化缓存 #

dockerfile
# 依赖阶段
FROM node:18 AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# 构建阶段
FROM node:18 AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# 运行阶段
FROM node:18-alpine
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/index.js"]

使用BuildKit #

dockerfile
# syntax=docker/dockerfile:1

FROM alpine AS base
RUN apk add --no-cache curl

FROM base AS builder
COPY src/ /src/
RUN build-script.sh

FROM base AS production
COPY --from=builder /app /app
CMD ["./app"]
bash
# 启用BuildKit
DOCKER_BUILDKIT=1 docker build -t myapp .

镜像大小对比 #

text
┌─────────────────────────────────────────────────────┐
│                 镜像大小对比                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  Go应用:                                            │
│  单阶段构建: ~1.2GB                                 │
│  多阶段构建: ~10MB                                  │
│                                                     │
│  Node.js应用:                                       │
│  单阶段构建: ~1GB                                   │
│  多阶段构建: ~150MB                                 │
│                                                     │
│  前端应用:                                          │
│  单阶段构建: ~1GB                                   │
│  多阶段构建: ~25MB                                  │
│                                                     │
└─────────────────────────────────────────────────────┘

最佳实践 #

1. 命名构建阶段 #

dockerfile
# 使用有意义的名称
FROM node:18 AS builder
FROM nginx:alpine AS production

2. 最小化运行阶段 #

dockerfile
# 使用最小基础镜像
FROM alpine:3.18
# 或
FROM scratch

3. 只复制必需文件 #

dockerfile
# 只复制需要的文件
COPY --from=builder /app/dist ./dist
# 不要复制整个目录
# COPY --from=builder /app .

4. 利用缓存 #

dockerfile
# 依赖阶段单独处理
FROM node:18 AS deps
COPY package*.json ./
RUN npm ci

# 构建阶段
FROM node:18 AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

小结 #

本节学习了Docker多阶段构建:

  • 多阶段构建的概念和优势
  • 基本语法和使用方法
  • 各种语言的实战示例
  • 高级技巧和最佳实践

下一步 #

接下来,让我们学习 Docker Swarm,了解Docker的集群编排功能。