多阶段构建

使用多阶段构建减小镜像体积,分离构建和运行环境


📋 目录


多阶段构建原理

为什么需要多阶段构建

传统构建方式的问题:

  • 镜像包含构建工具和依赖,体积大
  • 包含源代码,安全性差
  • 构建环境和运行环境混合

多阶段构建的优势:

  • 减小镜像体积:只保留运行时需要的文件
  • 提高安全性:不包含构建工具和源代码
  • 优化构建缓存:构建阶段和运行阶段分离

基本语法

基本结构

# 构建阶段
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

命名构建阶段

FROM node:16 AS builder
# ... 构建步骤
 
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html

构建阶段分离

示例:Node.js 应用

# 构建阶段
FROM node:16 AS builder
WORKDIR /app
 
# 复制依赖文件
COPY package*.json ./
RUN npm ci
 
# 复制源代码并构建
COPY . .
RUN npm run build
 
# 运行阶段
FROM node:16-alpine
WORKDIR /app
 
# 只复制构建产物和运行时依赖
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
 
CMD ["node", "dist/index.js"]

示例:Go 应用

# 构建阶段
FROM golang:1.18 AS builder
WORKDIR /app
 
# 下载依赖
COPY go.mod go.sum ./
RUN go mod download
 
# 构建应用
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
 
# 运行阶段
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
 
# 只复制编译好的二进制文件
COPY --from=builder /app/app .
CMD ["./app"]

示例:Python 应用

# 构建阶段
FROM python:3.9 AS builder
WORKDIR /app
 
# 安装依赖
COPY requirements.txt .
RUN pip install --user -r requirements.txt
 
# 运行阶段
FROM python:3.9-slim
WORKDIR /app
 
# 只复制已安装的包
COPY --from=builder /root/.local /root/.local
COPY . .
 
# 确保使用用户安装的包
ENV PATH=/root/.local/bin:$PATH
 
CMD ["python", "app.py"]

只复制必要文件

使用 —from 复制

# 从构建阶段复制文件
COPY --from=builder /app/dist /usr/share/nginx/html
 
# 从其他镜像复制文件
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/nginx.conf

复制多个文件

FROM builder AS builder
# ... 构建步骤
 
FROM nginx:alpine
# 复制多个文件
COPY --from=builder /app/dist /usr/share/nginx/html
COPY --from=builder /app/config /app/config

实际应用案例

案例 1:前端应用

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

案例 2:Java 应用

# 构建阶段
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
 
# 运行阶段
FROM openjdk:17-jre-slim
WORKDIR /app
COPY --from=builder /app/target/app.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

案例 3:多服务应用

# 共享构建阶段
FROM node:16 AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci
 
# API 构建阶段
FROM base AS api-builder
COPY api ./api
RUN npm run build:api
 
# Web 构建阶段
FROM base AS web-builder
COPY web ./web
RUN npm run build:web
 
# 运行阶段
FROM nginx:alpine
COPY --from=api-builder /app/api/dist /usr/share/nginx/api
COPY --from=web-builder /app/web/dist /usr/share/nginx/html

最佳实践

1. 命名构建阶段

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

2. 最小化最终镜像

# 只复制运行时需要的文件
COPY --from=builder /app/dist /usr/share/nginx/html
# 不要复制 node_modules、源代码等

3. 利用构建缓存

# 先复制依赖文件(变化频率低)
COPY package*.json ./
RUN npm install
 
# 再复制源代码(变化频率高)
COPY . .
RUN npm run build

4. 使用特定目标

# 只构建特定阶段
docker build --target builder -t myapp:builder .
 
# 构建最终镜像
docker build --target production -t myapp:latest .

5. 调试构建阶段

# 构建到特定阶段
docker build --target builder -t myapp:builder .
 
# 运行构建阶段进行调试
docker run -it myapp:builder /bin/bash

高级技巧

使用外部镜像作为源

# 从其他镜像复制文件
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=node:16 /usr/local/bin/node /usr/local/bin/

条件构建

# 根据构建参数选择阶段
ARG BUILD_ENV=production
FROM node:16 AS builder
# ... 构建步骤
 
FROM node:16-alpine AS dev
COPY --from=builder /app .
 
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
 
# 根据参数选择最终阶段
FROM ${BUILD_ENV}

📚 参考资源


相关笔记