长安的Docker教程长安的Docker教程
首页
快速开始
编程指南
首页
快速开始
编程指南
  • 🎯 Docker入门篇

    • 快速开始
    • Docker是什么?
    • 为什么要用Docker?
    • 安装Docker
    • 第一个容器
  • 📦 Docker基础篇

    • Docker镜像详解
    • 容器操作详解
    • 编写Dockerfile
    • 数据卷(Volumes)
    • Docker网络
  • ⚡ Docker进阶篇

    • Docker Compose
    • 多阶段构建
    • Docker性能优化
    • Docker最佳实践
  • 🚀 实战项目

    • 实战:部署Node.js应用
    • 实战:搭建数据库环境
    • 实战:微服务架构
  • 💡 常见问题

    • 常见问题排查
    • 实用技巧

多阶段构建

嘿!长安来教你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 --from=builder /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 --from=<阶段名> <源路径> <目标路径>
... 其他指令 ...

COPY --from的用法

# 从前一个阶段复制
COPY --from=builder /app/dist ./dist

# 从指定阶段复制
COPY --from=stage1 /app/file ./

# 从其他镜像复制(不用FROM)
COPY --from=nginx:latest /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 --from=builder /app/dist ./dist
COPY --from=builder /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 --from=builder /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 --from=builder /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 --from=builder /app/main /main
# 复制时区信息(可选)
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# 复制CA证书(HTTPS请求需要)
COPY --from=builder /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 --from=builder /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 --from=builder /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 --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/

# 从busybox复制工具
COPY --from=busybox:latest /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应用800MB15MB98%
Node.js API1.2GB150MB87%
React前端1.5GB25MB98%
Python Flask900MB200MB78%
Java Spring650MB180MB72%

平均减少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 --from=builder /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 --from=builder /app/dist ./dist
COPY --from=builder /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

💡 小结

今天长安教你精通多阶段构建:

核心概念

  1. 作用:减小镜像大小,分离构建和运行环境
  2. 语法:多个FROM,COPY --from
  3. 效果:镜像大小减少80%-98%

关键语法

# 定义阶段
FROM image AS stage-name

# 从阶段复制
COPY --from=stage-name /src /dest

# 指定构建目标
docker build --target stage-name

最佳实践

  1. 构建阶段用完整镜像,运行阶段用slim/alpine
  2. 利用构建缓存,先复制依赖文件
  3. 使用.dockerignore排除不必要的文件
  4. 清理构建缓存和临时文件
  5. 使用明确的版本号

🚀 下一步

镜像已经很小了,但还想更快?下一章长安教你 性能优化!


💬 长安的优化经验:

第一次知道多阶段构建的时候,我去把所有项目的Dockerfile都改了一遍😂

以前一个镜像1GB+,推送到仓库要等半天。 现在只有50MB,几秒钟就推送完了!

部署也快了,拉取镜像从5分钟变成30秒!

多阶段构建是Docker必学技巧,掌握了就是提升生产力!

下一章教你更多优化技巧,让Docker飞起来!💪

在 GitHub 上编辑此页
Prev
Docker Compose
Next
Docker性能优化