Skip to content

Docker 多阶段构建

多阶段构建是 Docker 的一个强大特性,允许在单个 Dockerfile 中使用多个 FROM 指令,从而优化镜像大小和构建效率。

🎯 多阶段构建概述

什么是多阶段构建

多阶段构建允许您:

  • 在构建阶段使用完整的开发环境
  • 在运行阶段使用精简的生产环境
  • 显著减少最终镜像大小
  • 提高安全性和性能

传统构建 vs 多阶段构建

dockerfile
# 传统构建 - 镜像较大
FROM node:16
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
dockerfile
# 多阶段构建 - 镜像精简
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
EXPOSE 3000
CMD ["npm", "start"]

🏗️ 基础语法

基本结构

dockerfile
# 第一阶段:构建阶段
FROM base-image AS stage-name
# 构建指令...

# 第二阶段:运行阶段
FROM runtime-image
COPY --from=stage-name /source /destination
# 运行指令...

阶段命名

dockerfile
# 使用 AS 关键字命名阶段
FROM golang:1.19 AS builder
FROM node:16 AS frontend-builder
FROM python:3.9 AS backend-builder

# 从命名阶段复制文件
COPY --from=builder /app/binary /usr/local/bin/
COPY --from=frontend-builder /app/dist /var/www/html/

🚀 实践示例

Go 应用多阶段构建

dockerfile
# 构建阶段
FROM golang:1.19-alpine AS builder

# 安装构建依赖
RUN apk add --no-cache git ca-certificates tzdata

# 设置工作目录
WORKDIR /app

# 复制 go mod 文件
COPY go.mod go.sum ./

# 下载依赖
RUN go mod download

# 复制源代码
COPY . .

# 构建应用
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags='-w -s -extldflags "-static"' \
    -o main .

# 运行阶段
FROM scratch

# 从构建阶段复制必要文件
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /app/main /main

# 暴露端口
EXPOSE 8080

# 运行应用
ENTRYPOINT ["/main"]

Node.js 应用多阶段构建

dockerfile
# 依赖安装阶段
FROM node:16-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# 构建阶段
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 运行阶段
FROM node:16-alpine AS runner
WORKDIR /app

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# 复制构建产物
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

USER nextjs

EXPOSE 3000
CMD ["npm", "start"]

Python 应用多阶段构建

dockerfile
# 构建阶段
FROM python:3.9-slim AS builder

# 安装构建依赖
RUN apt-get update && apt-get install -y \
    build-essential \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY requirements.txt .

# 安装 Python 依赖到临时目录
RUN pip install --user --no-cache-dir -r requirements.txt

# 运行阶段
FROM python:3.9-slim

# 创建非 root 用户
RUN useradd --create-home --shell /bin/bash app

# 从构建阶段复制依赖
COPY --from=builder /root/.local /home/app/.local

# 复制应用代码
COPY . /app
WORKDIR /app

# 切换到非 root 用户
USER app

# 设置 PATH
ENV PATH=/home/app/.local/bin:$PATH

EXPOSE 8000
CMD ["python", "app.py"]

React 应用多阶段构建

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

WORKDIR /app

# 复制 package 文件
COPY package*.json ./

# 安装依赖
RUN npm ci

# 复制源代码
COPY . .

# 构建应用
RUN npm run build

# 运行阶段
FROM nginx:alpine

# 复制构建产物
COPY --from=builder /app/build /usr/share/nginx/html

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

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

🔧 高级技巧

从外部镜像复制

dockerfile
# 从官方镜像复制文件
FROM alpine AS base
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/

# 从特定版本复制
COPY --from=node:16-alpine /usr/local/bin/node /usr/local/bin/

条件构建

dockerfile
FROM node:16 AS base

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

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

# 默认目标
FROM production

构建参数控制

dockerfile
ARG BUILD_ENV=production

FROM node:16 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 . .
RUN npm run build
CMD ["npm", "start"]

FROM ${BUILD_ENV} AS final

📊 构建优化

缓存优化

dockerfile
# 优化前 - 每次都重新安装依赖
FROM node:16
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build

# 优化后 - 利用层缓存
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

并行构建

dockerfile
# 前端构建
FROM node:16 AS frontend
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm install
COPY frontend/ .
RUN npm run build

# 后端构建
FROM golang:1.19 AS backend
WORKDIR /app/backend
COPY backend/go.* ./
RUN go mod download
COPY backend/ .
RUN go build -o server

# 最终镜像
FROM alpine:latest
COPY --from=frontend /app/frontend/dist /var/www/html
COPY --from=backend /app/backend/server /usr/local/bin/
CMD ["server"]

🛠️ 构建工具集成

Docker Buildx

bash
# 启用 BuildKit
export DOCKER_BUILDKIT=1

# 构建特定阶段
docker build --target development -t myapp:dev .
docker build --target production -t myapp:prod .

# 多平台构建
docker buildx build --platform linux/amd64,linux/arm64 -t myapp .

BuildKit 特性

dockerfile
# syntax=docker/dockerfile:1
FROM alpine AS base

# 缓存挂载
RUN --mount=type=cache,target=/var/cache/apk \
    apk add --update git

# 密钥挂载
RUN --mount=type=secret,id=mypassword \
    cat /run/secrets/mypassword

📈 性能对比

镜像大小对比

bash
# 单阶段构建
REPOSITORY    TAG       SIZE
myapp         single    1.2GB

# 多阶段构建
REPOSITORY    TAG       SIZE
myapp         multi     150MB

构建时间优化

dockerfile
# 优化构建时间
FROM node:16-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:16-alpine
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json ./
CMD ["npm", "start"]

🚀 最佳实践

1. 合理选择基础镜像

dockerfile
# 构建阶段使用完整镜像
FROM node:16 AS builder

# 运行阶段使用精简镜像
FROM node:16-alpine

2. 优化层缓存

dockerfile
# 先复制依赖文件
COPY package*.json ./
RUN npm install

# 再复制源代码
COPY . .

3. 最小化最终镜像

dockerfile
# 只复制必要文件
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

4. 使用非 root 用户

dockerfile
FROM alpine
RUN adduser -D -s /bin/sh appuser
USER appuser

5. 清理构建缓存

dockerfile
RUN apt-get update && apt-get install -y \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

通过多阶段构建,您可以创建更小、更安全、更高效的 Docker 镜像,提升应用的部署和运行效率。