Docker 多阶段构建¶
一句话概述:多阶段构建(Multi-stage Build)是 Dockerfile 的高级技巧,在一个 Dockerfile 中使用多个 FROM 指令,编译阶段用完整工具链,最终镜像只保留运行时需要的文件,大幅减小镜像体积。
核心知识点¶
| 概念 | 白话解释 |
|---|---|
| Stage | 阶段 = 每个 FROM 指令开始一个新阶段 |
| AS | 别名 = 给阶段取名字,方便后面引用 |
| COPY --from | 跨阶段复制 = 从前一个阶段复制文件到当前阶段 |
| Builder Pattern | 构建者模式 = 编译在一个阶段,运行在另一个阶段 |
| Scratch | 空镜像 = 最小的基础镜像,什么都没有 |
| Distroless | 无发行版镜像 = Google 提供的最小运行时镜像 |
基本使用¶
1. Python 应用多阶段构建¶
# === 阶段1:构建阶段(安装依赖) ===
FROM python:3.12-slim AS builder
# 使用完整 Python 镜像作为构建环境
WORKDIR /app
# 设置工作目录
COPY requirements.txt .
# 复制依赖文件
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 安装依赖到 /install 目录(不是系统默认位置)
# --no-cache-dir 不缓存下载,减小体积
# --prefix=/install 安装到指定目录,方便后面复制
# === 阶段2:运行阶段(最小镜像) ===
FROM python:3.12-slim
# 使用相同的 slim 镜像(但干净的,没有构建残留)
WORKDIR /app
COPY --from=builder /install /usr/local
# 从 builder 阶段复制已安装的依赖
COPY . .
# 复制应用代码
EXPOSE 8000
# 声明端口
CMD ["python", "app.py"]
# 启动命令
2. 生信工具构建¶
# === 阶段1:编译 samtools ===
FROM ubuntu:22.04 AS build-samtools
RUN apt-get update && apt-get install -y \
build-essential \
autoconf \
zlib1g-dev \
libbz2-dev \
liblzma-dev \
libcurl4-openssl-dev \
wget
# 安装编译所需的工具和库
RUN wget https://github.com/samtools/samtools/releases/download/1.19/samtools-1.19.tar.bz2 && \
tar xjf samtools-1.19.tar.bz2 && \
cd samtools-1.19 && \
./configure --prefix=/opt/samtools && \
make -j$(nproc) && \
make install
# 下载、编译、安装 samtools
# === 阶段2:只复制编译好的二进制文件 ===
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
zlib1g libbz2-1.0 liblzma5 libcurl4
# 只安装运行时需要的库(不装编译工具)
COPY --from=build-samtools /opt/samtools /opt/samtools
# 从构建阶段复制 samtools
ENV PATH="/opt/samtools/bin:$PATH"
# 添加到 PATH
CMD ["samtools", "--version"]
# 镜像大小从 ~800MB 降到 ~150MB
3. Go 应用(极致小镜像)¶
# === 阶段1:编译 ===
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
# 先下载依赖(利用缓存)
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server .
# 静态编译,不依赖 C 库
# === 阶段2:最小镜像 ===
FROM scratch
# scratch = 空镜像(0 字节)
COPY --from=builder /app/server /server
# 只复制编译好的二进制文件
ENTRYPOINT ["/server"]
# 最终镜像只有几 MB
高级用法¶
1. 多个构建目标¶
FROM python:3.12-slim AS base
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# 开发镜像
FROM base AS dev
RUN pip install pytest ruff # 额外装开发工具
COPY . .
CMD ["pytest"]
# 生产镜像
FROM base AS prod
COPY . .
CMD ["gunicorn", "app:app"]
docker build --target dev -t myapp:dev . # 构建开发镜像
docker build --target prod -t myapp:prod . # 构建生产镜像
2. 缓存优化¶
FROM python:3.12-slim AS builder
# 先复制依赖文件(变化少,利用缓存)
COPY requirements.txt .
RUN pip install --prefix=/install -r requirements.txt
# 后复制源码(变化多)
COPY . .
# 这样改代码不会重新安装依赖
3. 从外部镜像复制¶
FROM ubuntu:22.04
# 从其他镜像直接复制文件(不需要自己构建)
COPY --from=busybox:latest /bin/busybox /usr/local/bin/busybox
COPY --from=golang:1.22 /usr/local/go /usr/local/go
常见报错¶
| 报错信息 | 原因 | 解决方法 |
|---|---|---|
COPY --from: invalid from flag | 阶段名拼写错误 | 检查 AS 别名是否匹配 |
file not found | 复制的文件在构建阶段不存在 | 检查构建阶段的安装路径 |
exec format error | 二进制与目标平台不匹配 | 指定正确的 GOOS/GOARCH |
| 镜像很大没缩小 | 最终阶段引入了不必要的层 | 检查最终 FROM 之后的指令 |
速查表¶
# === 多阶段构建核心语法 ===
FROM image AS name # 定义命名阶段
COPY --from=name /src /dst # 从指定阶段复制
COPY --from=nginx:latest /etc/nginx /etc/nginx # 从外部镜像复制
# === 构建命令 ===
docker build . # 构建(运行所有阶段,输出最后一个)
docker build --target name . # 只构建到指定阶段
docker build --no-cache . # 不用缓存
# === 镜像大小对比 ===
# python:3.12 → ~1GB(完整版)
# python:3.12-slim → ~150MB(精简版)
# python:3.12-alpine → ~50MB(Alpine 版)
# golang + scratch → ~5-10MB(静态编译)
# distroless → ~20MB(Google 最小运行时)
# === 最佳实践 ===
# 1. 编译和运行分开(编译阶段装工具,运行阶段只装运行时)
# 2. 先 COPY 依赖文件,后 COPY 源码(利用缓存)
# 3. 用 .dockerignore 排除不需要的文件
# 4. 合并 RUN 指令减少层数
# 5. 选择合适的基础镜像(slim > full,alpine 要注意 musl 兼容性)
参考:Docker Multi-stage Builds | 更新于 2026 年