Docker多阶段构建
多阶段构建基础
多阶段构建是Docker的一个强大功能,允许在单个Dockerfile中使用多个FROM指令。每个FROM指令可以使用不同的基础镜像,并且可以从前面的阶段复制文件到后面的阶段。
为什么需要多阶段构建?
传统构建的问题
- 镜像过大:包含构建工具和源代码
- 安全风险:构建工具可能包含漏洞
- 资源浪费:运行时不需要构建工具
多阶段构建的优势
- 减小镜像大小:只包含运行时需要的文件
- 提高安全性:移除构建工具和源代码
- 优化构建过程:并行构建多个阶段
- 简化部署:生产镜像更轻量
基本语法
简单多阶段构建
dockerfile
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 生产阶段
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
命名阶段
dockerfile
# 依赖安装阶段
FROM node:16 AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 生产阶段
FROM node:16-alpine AS production
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./
EXPOSE 3000
CMD ["npm", "start"]
实践练习
练习1:Node.js应用多阶段构建
创建Dockerfile
:
dockerfile
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 安装依赖
RUN npm ci
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 生产阶段
FROM node:16-alpine AS production
# 创建非root用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
WORKDIR /app
# 复制构建产物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
# 设置权限
RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]
创建package.json
:
json
{
"name": "multi-stage-app",
"version": "1.0.0",
"description": "多阶段构建示例",
"main": "dist/app.js",
"scripts": {
"build": "tsc",
"start": "node dist/app.js",
"dev": "ts-node src/app.ts"
},
"dependencies": {
"express": "^4.18.0"
},
"devDependencies": {
"typescript": "^4.7.0",
"ts-node": "^10.8.0",
"@types/express": "^4.17.0"
}
}
练习2:Go应用多阶段构建
创建Dockerfile
:
dockerfile
# 构建阶段
FROM golang:1.19-alpine AS builder
# 安装必要的包
RUN apk add --no-cache git
WORKDIR /app
# 复制go mod文件
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:latest AS production
# 安装ca证书
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# 复制二进制文件
COPY --from=builder /app/main .
# 暴露端口
EXPOSE 8080
# 运行应用
CMD ["./main"]
练习3:Java应用多阶段构建
创建Dockerfile
:
dockerfile
# 构建阶段
FROM maven:3.8-openjdk-11 AS builder
WORKDIR /app
# 复制pom文件
COPY pom.xml .
# 下载依赖
RUN mvn dependency:go-offline -B
# 复制源代码
COPY src ./src
# 构建应用
RUN mvn clean package -DskipTests
# 生产阶段
FROM openjdk:11-jre-slim AS production
WORKDIR /app
# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 复制jar文件
COPY --from=builder /app/target/*.jar app.jar
# 设置权限
RUN chown -R appuser:appuser /app
USER appuser
# 暴露端口
EXPOSE 8080
# 运行应用
CMD ["java", "-jar", "app.jar"]
高级技巧
1. 条件构建
dockerfile
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# 根据构建参数决定构建方式
ARG BUILD_ENV=production
RUN if [ "$BUILD_ENV" = "development" ]; then \
npm run build:dev; \
else \
npm run build:prod; \
fi
# 生产阶段
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
2. 并行构建
dockerfile
# 前端构建
FROM node:16 AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm install
COPY frontend/ .
RUN npm run build
# 后端构建
FROM node:16 AS backend-builder
WORKDIR /app/backend
COPY backend/package*.json ./
RUN npm install
COPY backend/ .
RUN npm run build
# 生产阶段
FROM nginx:alpine AS production
COPY --from=frontend-builder /app/frontend/dist /usr/share/nginx/html
COPY --from=backend-builder /app/backend/dist /usr/share/nginx/api
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
3. 缓存优化
dockerfile
# 依赖阶段
FROM node:16 AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM node:16-alpine AS production
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./
EXPOSE 3000
CMD ["npm", "start"]
构建优化
1. 构建参数
dockerfile
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
# 构建参数
ARG NODE_ENV=production
ARG VERSION=1.0.0
# 环境变量
ENV NODE_ENV=$NODE_ENV
ENV VERSION=$VERSION
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 生产阶段
FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
构建命令:
bash
# 传递构建参数
docker build --build-arg NODE_ENV=production --build-arg VERSION=1.0.0 -t myapp .
2. 目标构建
bash
# 只构建到特定阶段
docker build --target builder -t myapp:builder .
# 构建生产镜像
docker build --target production -t myapp:production .
3. 构建缓存
bash
# 使用构建缓存
docker build --cache-from myapp:latest -t myapp .
# 不使用缓存
docker build --no-cache -t myapp .
最佳实践
1. 阶段命名
dockerfile
# 使用有意义的阶段名称
FROM node:16 AS dependencies
FROM node:16 AS builder
FROM nginx:alpine AS production
2. 最小化生产镜像
dockerfile
# 使用Alpine基础镜像
FROM nginx:alpine AS production
# 只复制必要的文件
COPY --from=builder /app/dist /usr/share/nginx/html
# 使用非root用户
RUN adduser -D -s /bin/sh appuser
USER appuser
3. 安全考虑
dockerfile
# 生产阶段不包含构建工具
FROM nginx:alpine AS production
# 不复制源代码
COPY --from=builder /app/dist /usr/share/nginx/html
# 使用只读文件系统
RUN apk add --no-cache dumb-init
ENTRYPOINT ["dumb-init", "--"]
性能优化
1. 并行构建
dockerfile
# 使用并行构建
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --parallel
COPY . .
RUN npm run build --parallel
2. 层缓存优化
dockerfile
# 先复制依赖文件
COPY package*.json ./
RUN npm install
# 再复制源代码
COPY . .
RUN npm run build
3. 构建上下文优化
创建.dockerignore
:
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
常见问题
1. 构建失败
bash
# 查看构建日志
docker build --no-cache --progress=plain -t myapp .
# 构建到特定阶段
docker build --target builder -t myapp:builder .
2. 镜像过大
bash
# 分析镜像大小
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# 查看镜像层
docker history myapp:latest
3. 构建缓慢
bash
# 使用构建缓存
docker build --cache-from myapp:latest -t myapp .
# 优化.dockerignore文件
# 使用并行构建
下一步
掌握多阶段构建后,您可以:
- 继续学习 Docker安全最佳实践
- 学习Docker镜像优化技巧
- 探索CI/CD中的多阶段构建
学习检查点
完成本章学习后,您应该能够:
- [ ] 理解多阶段构建的概念和优势
- [ ] 熟练编写多阶段Dockerfile
- [ ] 掌握构建优化技巧
- [ ] 了解多阶段构建的最佳实践
- [ ] 解决多阶段构建相关问题