引言

Docker镜像体积过大不仅影响部署速度,还增加存储成本和安全风险。多阶段构建(Multi-stage Build)是解决这个问题的利器。本文通过实际案例展示如何将Python应用镜像从1.2GB优化到120MB。

问题:传统构建的问题

先看一个典型的Python应用Dockerfile:

# 传统方式 - 1.2GB
FROM python:3.11

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8000
CMD ["python", "app.py"]

问题分析

解决方案:多阶段构建

基础版多阶段构建

# 第一阶段:构建
FROM python:3.11 AS builder

WORKDIR /app

# 安装编译依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    python3-dev \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# 第二阶段:运行
FROM python:3.11-slim AS runtime

WORKDIR /app

# 从builder阶段复制已安装的包
COPY --from=builder /root/.local /root/.local

# 确保使用本地安装的包
ENV PATH=/root/.local/bin:$PATH

COPY . .

EXPOSE 8000
CMD ["python", "app.py"]

效果:镜像体积从1.2GB降至380MB(减少68%)

进阶版:使用distroless

# 第一阶段:构建
FROM python:3.11 AS builder

WORKDIR /app

COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# 第二阶段:最小化运行环境
FROM gcr.io/distroless/python3-debian12

WORKDIR /app

# 复制Python包
COPY --from=builder /root/.local/lib/python3.11/site-packages /app/lib
ENV PYTHONPATH=/app/lib

# 复制应用代码
COPY app.py .

EXPOSE 8000
CMD ["app.py"]

效果:镜像体积降至120MB(减少90%)

Node.js应用优化

多阶段构建同样适用于Node.js:

# 第一阶段:依赖安装和构建
FROM node:18-alpine AS builder

WORKDIR /app

# 安装依赖
COPY package*.json ./
RUN npm ci --only=production

# 第二阶段:生产镜像
FROM node:18-alpine AS production

WORKDIR /app

# 创建非root用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# 仅复制必要文件
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --chown=nextjs:nodejs . .

USER nextjs

EXPOSE 3000
ENV PORT 3000

CMD ["node", "server.js"]

Go应用优化

Go应用的多阶段构建效果最显著:

# 第一阶段:编译
FROM golang:1.21-alpine AS builder

WORKDIR /app

# 安装git(如果需要)
RUN apk add --no-cache git

# 复制依赖文件
COPY go.mod go.sum ./
RUN go mod download

# 复制源码并编译
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# 第二阶段:最小镜像
FROM scratch

WORKDIR /root/

# 从builder复制二进制文件
COPY --from=builder /app/main .

# 如果需要HTTPS支持
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080

CMD ["./main"]

效果:Go应用镜像可降至15MB以下

Java应用优化

Spring Boot应用的多阶段构建:

# 第一阶段:构建
FROM maven:3.9-eclipse-temurin-17-alpine AS builder

WORKDIR /app

COPY pom.xml .
RUN mvn dependency:go-offline

COPY src ./src
RUN mvn package -DskipTests

# 第二阶段:运行
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

# 创建非root用户
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring

# 复制jar文件
COPY --from=builder /app/target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "/app/app.jar"]

优化技巧汇总

1. 使用.dockerignore

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.vscode
.idea
*.md

2. 合并RUN指令减少层数

# 不推荐
RUN apt-get update
RUN apt-get install -y python3
RUN apt-get install -y gcc

# 推荐
RUN apt-get update && apt-get install -y \
    python3 \
    gcc \
    && rm -rf /var/lib/apt/lists/*

3. 使用BuildKit缓存

# syntax=docker/dockerfile:1
FROM python:3.11

# 启用pip缓存
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

构建命令:

DOCKER_BUILDKIT=1 docker build --progress=plain -t myapp .

4. 并行构建多架构镜像

docker buildx create --use
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myapp:latest \
  --push .

性能对比

优化方式 镜像大小 构建时间 启动时间
传统构建 1.2GB 45s 8s
多阶段基础版 380MB 52s 3s
多阶段+distroless 120MB 55s 1s
Go+Scratch 15MB 30s 0.5s

安全检查清单

# 安全最佳实践
FROM python:3.11-slim

# 1. 使用非root用户
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
USER appuser

# 2. 只安装必要的包
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 \
    && rm -rf /var/lib/apt/lists/*

# 3. 复制特定文件而非整个目录
COPY --chown=appuser:appgroup app.py requirements.txt ./

# 4. 使用只读文件系统(配合tmpfs)
# docker run --read-only --tmpfs /tmp myapp

# 5. 限制资源
# docker run --memory=512m --cpus=1.0 myapp

总结

多阶段构建是优化Docker镜像的核心技术:

  1. 分离构建和运行环境 - 编译工具不进入生产镜像
  2. 选择合适的基础镜像 - alpine或distroless
  3. 使用.dockerignore - 排除不必要的文件
  4. 合并RUN指令 - 减少镜像层数
  5. 启用BuildKit - 利用缓存加速构建
  6. 安全加固 - 非root用户、最小权限

💡 工具推荐:如果你需要批量处理Docker镜像或监控容器运行状态,可以试试PySecToolkit——一个Python安全工具包,包含端口扫描、网络发现、文件安全扫描等功能,非常适合容器安全检查和运维自动化。


本文首发于 WD Tech Blog,转载请注明出处。