Skip to content

构建优化技巧

Docker 镜像构建优化是提高开发效率、减小镜像体积、提升部署速度的关键。本文将详细介绍各种构建优化技巧和最佳实践。

目录

  1. 优化概述
  2. 镜像体积优化
  3. 构建速度优化
  4. 缓存优化
  5. 多阶段构建优化
  6. 网络优化
  7. 安全优化

优化概述

1.1 优化目标

Docker 构建优化目标:

┌─────────────────────────────────────────┐
│  1. 减小镜像体积                         │
│     - 更快的拉取/推送速度                 │
│     - 更少的存储占用                      │
│     - 更快的部署启动                      │
├─────────────────────────────────────────┤
│  2. 提高构建速度                         │
│     - 更快的开发迭代                      │
│     - 更短的 CI/CD 时间                   │
│     - 更好的开发体验                      │
├─────────────────────────────────────────┤
│  3. 提高安全性                           │
│     - 更小的攻击面                        │
│     - 更少的漏洞                          │
│     - 更好的可维护性                      │
└─────────────────────────────────────────┘

1.2 优化指标

指标优化前优化后改进
镜像大小1 GB100 MB90% ↓
构建时间10 分钟2 分钟80% ↓
层数30+1067% ↓
漏洞数100+< 1090% ↓

镜像体积优化

2.1 选择合适的基础镜像

dockerfile
# ❌ 不推荐 - 体积过大
FROM ubuntu:22.04          # ~80 MB
FROM node:18               # ~1 GB
FROM python:3.11           # ~900 MB

# ✅ 推荐 - 体积适中
FROM node:18-slim          # ~200 MB
FROM python:3.11-slim      # ~120 MB
FROM debian:bookworm-slim  # ~75 MB

# ✅ 最佳 - 体积最小
FROM node:18-alpine        # ~180 MB
FROM python:3.11-alpine    # ~50 MB
FROM scratch               # ~0 MB (静态二进制)
FROM gcr.io/distroless     # ~20 MB

基础镜像对比:

镜像大小特点适用场景
ubuntu:22.0478 MB完整工具链需要 apt 包管理
debian:bookworm-slim75 MB稳定、安全生产环境
alpine:3.187 MB超轻量静态链接应用
scratch0 MB空镜像完全静态二进制
distroless20 MBGoogle 维护生产环境应用

2.2 多阶段构建

dockerfile
# 单阶段构建 - 体积大
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o myapp
CMD ["./myapp"]
# 结果: ~1.2 GB

# 多阶段构建 - 体积小
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o myapp

FROM alpine:3.18
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
CMD ["./myapp"]
# 结果: ~20 MB

2.3 清理构建产物

dockerfile
# ❌ 不推荐 - 残留缓存
RUN apt-get update && apt-get install -y \
    build-essential \
    curl \
    git

# ✅ 推荐 - 同层清理
RUN apt-get update && apt-get install -y \
    build-essential \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

# ✅ 更好 - 使用多阶段
FROM debian:bookworm-slim AS builder
RUN apt-get update && apt-get install -y build-essential
# ... 构建步骤

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/dist /app

2.4 使用 .dockerignore

dockerignore
# .dockerignore 优化示例

# 忽略所有
**

# 允许必要文件
!package*.json
!src/
!public/
!tsconfig.json
!vite.config.ts

# 但忽略开发文件
src/**/*.test.ts
src/**/*.spec.ts
src/**/__tests__/
src/**/__mocks__/

# 忽略配置
.git
.gitignore
.env*
*.md
docker-compose*.yml
Dockerfile*

# 忽略 IDE
.vscode
.idea
*.swp

# 忽略依赖和构建产物
node_modules
dist
build
coverage
.nyc_output

构建速度优化

3.1 优化指令顺序

dockerfile
# ❌ 不推荐 - 每次代码变更都重新安装依赖
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build

# ✅ 推荐 - 利用缓存层
FROM node:18-alpine
WORKDIR /app

# 1. 先复制依赖文件(变化频率低)
COPY package*.json ./
RUN npm ci

# 2. 后复制代码(变化频率高)
COPY . .
RUN npm run build

3.2 使用 BuildKit 缓存挂载

dockerfile
# syntax=docker/dockerfile:1

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./

# 使用缓存挂载加速 npm install
RUN --mount=type=cache,target=/root/.npm \
    npm ci

COPY . .
RUN npm run build

3.3 并行构建

dockerfile
# syntax=docker/dockerfile:1

# 并行构建前端和后端
FROM node:18-alpine AS frontend-builder
WORKDIR /frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build

FROM golang:1.21-alpine AS backend-builder
WORKDIR /backend
COPY backend/go.mod backend/go.sum ./
RUN go mod download
COPY backend/ .
RUN go build -o server

# 最终镜像
FROM alpine:3.18
COPY --from=frontend-builder /frontend/dist ./static
COPY --from=backend-builder /backend/server .
CMD ["./server"]

3.4 使用 BuildKit 并行执行

dockerfile
# syntax=docker/dockerfile:1

FROM alpine AS base

FROM base AS stage1
RUN sleep 5 && echo "Stage 1" > /tmp/stage1

FROM base AS stage2
RUN sleep 5 && echo "Stage 2" > /tmp/stage2

FROM base AS stage3
RUN sleep 5 && echo "Stage 3" > /tmp/stage3

FROM alpine AS final
COPY --from=stage1 /tmp/stage1 /tmp/
COPY --from=stage2 /tmp/stage2 /tmp/
COPY --from=stage3 /tmp/stage3 /tmp/

缓存优化

4.1 缓存策略

dockerfile
# 策略 1: 稳定指令在前
FROM python:3.11-slim

# 系统依赖(很少变化)
RUN apt-get update && apt-get install -y \
    gcc \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# Python 依赖(偶尔变化)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 应用代码(经常变化)
COPY . .

4.2 BuildKit 缓存导出

bash
# 导出缓存到本地
docker buildx build \
  --cache-to type=local,dest=/tmp/.buildx-cache,mode=max \
  -t myapp:latest .

# 导入缓存
docker buildx build \
  --cache-from type=local,src=/tmp/.buildx-cache \
  -t myapp:latest .

# 推送到仓库的缓存
docker buildx build \
  --cache-to type=registry,ref=myregistry/myapp:cache,mode=max \
  --cache-from type=registry,ref=myregistry/myapp:cache \
  -t myregistry/myapp:latest \
  --push .

# 内联缓存
docker buildx build \
  --cache-to type=inline \
  --cache-from myregistry/myapp:latest \
  -t myregistry/myapp:latest \
  --push .

4.3 GitHub Actions 缓存

yaml
# .github/workflows/build.yml
name: Build
on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      
      - name: Cache Docker layers
        uses: actions/cache@v3
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-
      
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: false
          tags: myapp:latest
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
      
      - name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

多阶段构建优化

5.1 优化阶段数量

dockerfile
# ❌ 过多阶段
FROM node:18-alpine AS deps
# ...

FROM node:18-alpine AS test
# ...

FROM node:18-alpine AS lint
# ...

FROM node:18-alpine AS build
# ...

FROM node:18-alpine AS production
# ...

# ✅ 合并相关阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run lint && npm run test && npm run build

FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]

5.2 条件构建目标

dockerfile
# syntax=docker/dockerfile:1

FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM base AS development
ENV NODE_ENV=development
COPY . .
CMD ["npm", "run", "dev"]

FROM base AS build
COPY . .
RUN npm run build

FROM node:18-alpine AS production
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production
CMD ["node", "dist/main.js"]

构建命令:

bash
# 开发构建
docker build --target development -t myapp:dev .

# 生产构建
docker build --target production -t myapp:prod .

5.3 使用缓存阶段

dockerfile
# syntax=docker/dockerfile:1

# 依赖缓存阶段
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# 生产阶段
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]

网络优化

6.1 使用镜像代理

json
// daemon.json
{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com",
    "https://mirror.baidubce.com"
  ]
}

6.2 使用私有仓库

dockerfile
# 使用私有仓库加速
FROM myregistry.com/base/node:18-alpine

# 或使用本地缓存镜像
FROM localhost:5000/node:18-alpine

6.3 离线构建

bash
# 预拉取基础镜像
docker pull node:18-alpine
docker tag node:18-alpine localhost:5000/node:18-alpine
docker push localhost:5000/node:18-alpine

# 离线构建
docker build --build-arg BASE_IMAGE=localhost:5000/node:18-alpine .

安全优化

7.1 最小权限原则

dockerfile
# ❌ 使用 root 运行
FROM node:18-alpine
WORKDIR /app
COPY . .
CMD ["node", "server.js"]

# ✅ 使用非 root 用户
FROM node:18-alpine
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001
WORKDIR /app
COPY --chown=nodejs:nodejs . .
USER nodejs
CMD ["node", "server.js"]

7.2 扫描镜像漏洞

bash
# 使用 Docker Scout
docker scout cves myapp:latest

# 使用 Trivy
trivy image myapp:latest

# 使用 Snyk
docker scan myapp:latest

7.3 安全构建选项

dockerfile
# 使用只读根文件系统
FROM node:18-alpine
WORKDIR /app
COPY . .
USER node
# 运行时添加: --read-only

# 使用安全选项
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"

# 最小化能力
# docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp

性能监控

8.1 分析构建性能

bash
# 查看构建详情
docker buildx build --progress=plain . 2>&1 | tee build.log

# 使用 dive 分析镜像
dive myapp:latest

# 查看镜像层大小
docker history myapp:latest

8.2 构建时间分析

bash
# 记录构建时间
time docker build -t myapp:latest .

# 使用 BuildKit 详细输出
DOCKER_BUILDKIT=1 docker build --progress=plain . 2>&1 | grep -E "(CACHED|RUN|COPY)"

优化检查清单

9.1 构建前检查

  • [ ] 使用 .dockerignore 排除不必要文件
  • [ ] 选择合适的基础镜像
  • [ ] 优化 Dockerfile 指令顺序
  • [ ] 使用多阶段构建
  • [ ] 配置构建缓存

9.2 构建中检查

  • [ ] 监控构建时间
  • [ ] 检查缓存命中率
  • [ ] 分析镜像层大小
  • [ ] 验证安全扫描结果

9.3 构建后检查

  • [ ] 验证镜像功能
  • [ ] 检查镜像大小
  • [ ] 扫描安全漏洞
  • [ ] 测试启动时间

下一步

基于 MIT 许可发布