多阶段构建
嘿!长安来教你Docker的杀手锏——多阶段构建!学会这个,镜像大小能减少90%!🚀
🤔 什么是多阶段构建?
简单说:在一个Dockerfile里使用多个FROM,每个FROM开始一个新阶段。
听起来抽象?没关系,长安给你讲明白!
😱 传统方式的问题
问题场景:编译型语言
比如编译一个Go程序:
传统Dockerfile:
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o myapp
CMD ["./myapp"]
构建镜像:
docker build -t myapp .
docker images myapp
结果:
REPOSITORY TAG SIZE
myapp latest 800MB 😱
800MB! 为什么这么大?
因为镜像里包含了:
- 整个Go编译环境(400MB+)
- Go源代码
- 编译工具链
- 各种依赖
但实际运行时只需要编译好的可执行文件(10MB)!
✨ 多阶段构建的解决方案
# 阶段1:编译阶段
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# 阶段2:运行阶段
FROM alpine:latest
WORKDIR /app
COPY /app/myapp .
CMD ["./myapp"]
构建镜像:
docker build -t myapp .
docker images myapp
结果:
REPOSITORY TAG SIZE
myapp latest 15MB 🎉
从800MB降到15MB! 减少了98%!
📚 多阶段构建详解
基本语法
# 阶段1
FROM <镜像> AS <阶段名>
... 构建指令 ...
# 阶段2
FROM <镜像>
COPY <源路径> <目标路径>
... 其他指令 ...
COPY --from的用法
# 从前一个阶段复制
COPY /app/dist ./dist
# 从指定阶段复制
COPY /app/file ./
# 从其他镜像复制(不用FROM)
COPY /etc/nginx/nginx.conf ./
🎯 实战案例
案例1:Node.js应用
项目结构:
my-node-app/
├── Dockerfile
├── package.json
├── package-lock.json
└── src/
└── index.js
不好的Dockerfile(单阶段):
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]
# 镜像大小:1.2GB 😱
好的Dockerfile(多阶段):
# 阶段1:安装依赖和构建
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 阶段2:运行
FROM node:18-alpine
WORKDIR /app
# 只复制必要的文件
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY package.json ./
USER node
CMD ["node", "dist/index.js"]
# 镜像大小:150MB 🎉
案例2:React前端应用
# 阶段1:构建
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生成静态文件到 /app/build
# 阶段2:运行(Nginx提供静态文件)
FROM nginx:alpine
COPY /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# 镜像大小:25MB 🎉
# 从1GB+降到25MB!
案例3:Python应用
# 阶段1:安装依赖
FROM python:3.11 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# 阶段2:运行
FROM python:3.11-slim
WORKDIR /app
# 复制Python包
COPY /root/.local /root/.local
COPY . .
# 更新PATH
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
案例4:Go微服务
# 阶段1:构建
FROM golang:1.21-alpine AS builder
WORKDIR /app
# 先复制go.mod和go.sum(利用缓存)
COPY go.mod go.sum ./
RUN go mod download
# 再复制源代码
COPY . .
# 编译(生成静态链接的二进制文件)
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# 阶段2:运行(使用scratch空镜像)
FROM scratch
COPY /app/main /main
# 复制时区信息(可选)
COPY /usr/share/zoneinfo /usr/share/zoneinfo
# 复制CA证书(HTTPS请求需要)
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
CMD ["/main"]
# 镜像大小:10MB 🎉
# 最小化镜像!
🚀 进阶技巧
技巧1:构建时选择不同阶段
# 开发阶段
FROM node:18 AS development
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
# 构建阶段
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM nginx:alpine AS production
COPY /app/dist /usr/share/nginx/html
使用:
# 开发环境(停在development阶段)
docker build --target development -t myapp:dev .
# 生产环境(默认到最后)
docker build -t myapp:prod .
技巧2:共享基础阶段
# 基础阶段
FROM python:3.11-slim AS base
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends gcc
ENV PYTHONUNBUFFERED=1
# 开发阶段
FROM base AS development
COPY requirements-dev.txt .
RUN pip install -r requirements-dev.txt
COPY . .
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
# 生产阶段
FROM base AS production
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]
技巧3:测试阶段
# 构建阶段
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 测试阶段
FROM builder AS test
RUN npm run test
RUN npm run lint
# 生产阶段
FROM nginx:alpine AS production
COPY /app/dist /usr/share/nginx/html
CI/CD中使用:
# 运行测试
docker build --target test -t myapp:test .
# 测试通过后构建生产镜像
docker build --target production -t myapp:prod .
技巧4:从外部镜像复制文件
FROM alpine:latest
# 从nginx镜像复制配置
COPY /etc/nginx/nginx.conf /etc/nginx/
# 从busybox复制工具
COPY /bin/busybox /bin/
CMD ["/bin/sh"]
💡 最佳实践
1. 合理安排阶段顺序
# ✅ 好:利用Docker缓存
FROM node:18 AS builder
COPY package*.json ./ # 先复制依赖文件
RUN npm install # 依赖不变时,这层会被缓存
COPY . . # 再复制源代码
RUN npm run build
2. 使用.dockerignore
# .dockerignore
node_modules
npm-debug.log
.git
.env
*.md
.DS_Store
dist
build
3. 选择合适的基础镜像
# 构建阶段:用完整镜像(工具全)
FROM node:18 AS builder
# 运行阶段:用alpine或slim(更小)
FROM node:18-alpine
4. 清理构建缓存
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && \
npm cache clean --force # 清理npm缓存
5. 使用明确的版本
# ❌ 不好
FROM node:latest
# ✅ 好
FROM node:18.17.0-alpine3.18
📊 对比测试
长安给你做了个对比:
| 项目类型 | 单阶段大小 | 多阶段大小 | 减少比例 |
|---|---|---|---|
| Go应用 | 800MB | 15MB | 98% |
| Node.js API | 1.2GB | 150MB | 87% |
| React前端 | 1.5GB | 25MB | 98% |
| Python Flask | 900MB | 200MB | 78% |
| Java Spring | 650MB | 180MB | 72% |
平均减少85%以上! 🎉
🎨 完整示例:全栈应用
项目结构:
fullstack-app/
├── frontend/
│ ├── package.json
│ └── src/
├── backend/
│ ├── package.json
│ └── src/
└── docker-compose.yml
frontend/Dockerfile:
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
backend/Dockerfile:
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
docker-compose.yml:
version: '3.8'
services:
frontend:
build: ./frontend
ports:
- "80:80"
backend:
build: ./backend
ports:
- "3000:3000"
environment:
NODE_ENV: production
💡 小结
今天长安教你精通多阶段构建:
核心概念
- 作用:减小镜像大小,分离构建和运行环境
- 语法:多个FROM,COPY --from
- 效果:镜像大小减少80%-98%
关键语法
# 定义阶段
FROM image AS stage-name
# 从阶段复制
COPY /src /dest
# 指定构建目标
docker build --target stage-name
最佳实践
- 构建阶段用完整镜像,运行阶段用slim/alpine
- 利用构建缓存,先复制依赖文件
- 使用.dockerignore排除不必要的文件
- 清理构建缓存和临时文件
- 使用明确的版本号
🚀 下一步
镜像已经很小了,但还想更快?下一章长安教你 性能优化!
💬 长安的优化经验:
第一次知道多阶段构建的时候,我去把所有项目的Dockerfile都改了一遍😂
以前一个镜像1GB+,推送到仓库要等半天。 现在只有50MB,几秒钟就推送完了!
部署也快了,拉取镜像从5分钟变成30秒!
多阶段构建是Docker必学技巧,掌握了就是提升生产力!
下一章教你更多优化技巧,让Docker飞起来!💪
