镜像构建与优化 #

镜像构建基础 #

docker build命令 #

bash
# 基本构建
docker build -t myapp:v1.0 .

# 指定Dockerfile路径
docker build -t myapp:v1.0 -f Dockerfile.prod .

# 指定构建上下文
docker build -t myapp:v1.0 /path/to/context

# 从URL构建
docker build -t myapp:v1.0 https://github.com/user/repo.git

# 从标准输入构建
cat Dockerfile | docker build -t myapp:v1.0 -

构建参数 #

bash
# 传递构建参数
docker build --build-arg VERSION=1.0.0 -t myapp:v1.0 .

# 指定目标阶段
docker build --target builder -t myapp:builder .

# 不使用缓存
docker build --no-cache -t myapp:v1.0 .

# 指定平台
docker build --platform linux/amd64 -t myapp:v1.0 .

BuildKit #

启用BuildKit #

bash
# 临时启用
DOCKER_BUILDKIT=1 docker build -t myapp:v1.0 .

# 永久启用(添加到 ~/.bashrc 或 ~/.zshrc)
export DOCKER_BUILDKIT=1

# 或在Docker Desktop中启用
# Settings > Docker Engine > 添加 "features": {"buildkit": true}

BuildKit优势 #

特性 说明
并行构建 独立层可并行构建
增量构建 只重建变化的部分
缓存优化 更智能的缓存策略
安全增强 支持secrets挂载
输出格式 支持多种输出格式

BuildKit示例 #

dockerfile
# syntax=docker/dockerfile:1

FROM alpine AS base
RUN apk add --no-cache curl

FROM base AS builder
COPY src/ /src/
RUN build-script.sh

FROM base AS production
COPY --from=builder /app /app
CMD ["./app"]

多阶段构建 #

基本概念 #

多阶段构建允许在一个Dockerfile中使用多个FROM语句,每个FROM开始一个新的构建阶段。

text
┌─────────────────────────────────────────────────────┐
│                   多阶段构建                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  Stage 1 (builder):                                 │
│  ┌─────────────────────────────────────────────┐   │
│  │  FROM golang:1.20 AS builder                │   │
│  │  WORKDIR /app                               │   │
│  │  COPY . .                                   │   │
│  │  RUN go build -o myapp                      │   │
│  └─────────────────────────────────────────────┘   │
│                      │                              │
│                      ↓ COPY --from=builder          │
│                                                     │
│  Stage 2 (production):                              │
│  ┌─────────────────────────────────────────────┐   │
│  │  FROM alpine:3.18                           │   │
│  │  COPY --from=builder /app/myapp /app/       │   │
│  │  CMD ["./myapp"]                            │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
└─────────────────────────────────────────────────────┘

Go应用示例 #

dockerfile
# 构建阶段
FROM golang:1.20-alpine AS builder

WORKDIR /app

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

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

# 运行阶段
FROM alpine:3.18

RUN apk --no-cache add ca-certificates tzdata

WORKDIR /root/

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

EXPOSE 8080
CMD ["./main"]

前端应用示例 #

dockerfile
# 构建阶段
FROM node:18-alpine 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

# 复制nginx配置
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

选择特定阶段构建 #

bash
# 只构建到builder阶段
docker build --target builder -t myapp:builder .

# 构建最终镜像
docker build -t myapp:v1.0 .

镜像优化技巧 #

1. 选择合适的基础镜像 #

dockerfile
# 不推荐: 完整镜像
FROM ubuntu:22.04          # ~77MB

# 推荐: slim镜像
FROM python:3.11-slim      # ~150MB

# 更推荐: alpine镜像
FROM python:3.11-alpine    # ~50MB

# 最小: scratch(空镜像)
FROM scratch               # 0MB

2. 减少镜像层数 #

dockerfile
# 不推荐: 多个RUN命令
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN apt-get clean

# 推荐: 合并命令
RUN apt-get update && apt-get install -y \
    curl \
    vim \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

3. 利用构建缓存 #

dockerfile
# 不推荐: 先复制所有文件
COPY . /app
RUN npm install

# 推荐: 先复制依赖文件
COPY package*.json ./
RUN npm install
COPY . .

4. 清理不必要的文件 #

dockerfile
RUN apt-get update && apt-get install -y \
    curl \
    vim \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* \
    && rm -rf /tmp/* \
    && rm -rf /var/tmp/*

5. 使用.dockerignore #

text
# .dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
*.md
.env
coverage
.nyc_output

6. 使用多阶段构建 #

dockerfile
# 构建阶段包含构建工具
FROM node:18 AS builder
RUN npm install -g typescript
COPY . .
RUN npm run build

# 生产阶段只包含运行时
FROM node:18-alpine
COPY --from=builder /app/dist /app/dist
CMD ["node", "/app/dist/index.js"]

7. 压缩镜像层 #

dockerfile
# 使用squash选项(需要启用experimental)
# docker build --squash -t myapp:v1.0 .

# 或在Dockerfile中合并层
RUN commands && \
    more commands && \
    cleanup

镜像大小对比 #

优化前后对比 #

text
┌─────────────────────────────────────────────────────┐
│                   镜像大小对比                       │
├─────────────────────────────────────────────────────┤
│                                                     │
│  优化前:                                            │
│  FROM ubuntu:22.04                                  │
│  RUN apt-get update                                 │
│  RUN apt-get install -y nodejs npm                  │
│  COPY . /app                                        │
│  CMD ["node", "app.js"]                             │
│  大小: ~500MB                                       │
│                                                     │
│  优化后:                                            │
│  FROM node:18-alpine                                │
│  WORKDIR /app                                       │
│  COPY package*.json ./                              │
│  RUN npm ci --only=production                       │
│  COPY . .                                           │
│  CMD ["node", "app.js"]                             │
│  大小: ~120MB                                       │
│                                                     │
│  多阶段构建:                                        │
│  FROM node:18-alpine AS builder                     │
│  WORKDIR /app                                       │
│  COPY . .                                           │
│  RUN npm ci && npm run build                        │
│                                                     │
│  FROM node:18-alpine                                │
│  COPY --from=builder /app/dist /app                 │
│  CMD ["node", "/app/index.js"]                      │
│  大小: ~50MB                                        │
│                                                     │
└─────────────────────────────────────────────────────┘

构建缓存优化 #

缓存机制 #

text
┌─────────────────────────────────────────────────────┐
│                   构建缓存机制                       │
├─────────────────────────────────────────────────────┤
│                                                     │
│  Dockerfile:                                        │
│  1. FROM node:18           ← 检查基础镜像           │
│  2. WORKDIR /app           ← 检查命令               │
│  3. COPY package*.json ./   ← 检查文件是否变化      │
│  4. RUN npm install        ← 检查命令和上下文       │
│  5. COPY . .               ← 检查文件是否变化       │
│  6. RUN npm run build      ← 检查命令和上下文       │
│                                                     │
│  如果某层缓存失效,后续所有层都需要重建             │
│                                                     │
└─────────────────────────────────────────────────────┘

缓存最佳实践 #

dockerfile
# 1. 把不常变化的放前面
FROM node:18-alpine
WORKDIR /app

# 2. 先复制依赖文件
COPY package*.json ./
RUN npm ci --only=production

# 3. 后复制经常变化的源代码
COPY src/ ./src/
COPY public/ ./public/

# 4. 最后执行构建
RUN npm run build

使用外部缓存 #

bash
# 从镜像拉取缓存
docker build --cache-from myapp:v1.0 -t myapp:v2.0 .

# 使用多个缓存源
docker build \
  --cache-from myapp:v1.0 \
  --cache-from myapp:latest \
  -t myapp:v2.0 .

构建性能优化 #

并行构建 #

dockerfile
# syntax=docker/dockerfile:1

FROM alpine AS base
RUN apk add --no-cache curl

# 并行执行
FROM base AS builder1
RUN build-part1.sh

FROM base AS builder2
RUN build-part2.sh

# 合并结果
FROM base AS final
COPY --from=builder1 /part1 /
COPY --from=builder2 /part2 /

使用BuildKit secrets #

dockerfile
# syntax=docker/dockerfile:1

FROM alpine

# 挂载secrets(不会保存在镜像中)
RUN --mount=type=secret,id=npm_token \
    export NPM_TOKEN=$(cat /run/secrets/npm_token) && \
    npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN && \
    npm install
bash
# 构建时传入secret
docker build \
  --secret id=npm_token,src=./.npm_token \
  -t myapp:v1.0 .

使用BuildKit ssh #

dockerfile
# syntax=docker/dockerfile:1

FROM alpine

# 挂载ssh agent
RUN --mount=type=ssh \
    apk add git && \
    git clone git@github.com:user/repo.git
bash
# 构建时使用ssh
docker build --ssh default -t myapp:v1.0 .

镜像分析工具 #

docker history #

bash
# 查看镜像层历史
docker history myapp:v1.0

# 不截断输出
docker history --no-trunc myapp:v1.0

# 格式化输出
docker history --format "{{.CreatedBy}}\t{{.Size}}" myapp:v1.0

dive工具 #

bash
# 安装dive
brew install dive

# 分析镜像
dive myapp:v1.0

# CI模式
CI=true dive myapp:v1.0

docker scout #

bash
# 分析镜像
docker scout quickview myapp:v1.0

# 查看漏洞
docker scout cves myapp:v1.0

# 比较镜像
docker scout compare myapp:v1.0 myapp:v2.0

小结 #

本节学习了Docker镜像的构建和优化:

  • BuildKit的使用
  • 多阶段构建
  • 镜像优化技巧
  • 构建缓存优化
  • 构建性能优化

下一步 #

接下来,让我们学习 镜像分发与存储,了解镜像的存储原理和分发策略。