Docker BuildKit + cache mount:CI build 从 8 分钟降到 90 秒

起因

我们的 Node + Python 微服务 CI 每次 build:

  1. apt install 系统依赖:1 分钟
  2. npm ci 装 node_modules:2.5 分钟
  3. pip install 装 Python 依赖:1.5 分钟
  4. webpack 编译:2 分钟
  5. push image:1 分钟

总 8 分钟。改一行代码 → 等 8 分钟。每天 30 次 build = 4 小时浪费。

BuildKit 是 Docker 的新 builder(默认开启),支持 cache mount /
secret mount / 并行 stage 构建。配合 CI 端 layer 缓存,
重复 build 大部分步骤跳过。

解决方案

1. 启用 BuildKit

# 现代 Docker Desktop / docker 23+ 默认开
# 如果没开:
export DOCKER_BUILDKIT=1
docker build .

# Compose 用 buildx:
docker compose build --progress plain

2. cache mount for package managers

旧 Dockerfile:

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci                  # 每次都从零下载 + COPY . .
RUN npm run build

每改一行代码 → COPY 之后所有 layer 失效 → npm ci 重新下整套依赖。

加 cache mount:

# syntax=docker/dockerfile:1.7
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
    npm ci --prefer-offline
COPY . .
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
    npm run build

--mount=type=cache 创建一个跨 build 持久的 cache 目录(在 buildkit
之内,不进最终 image)。npm 下载的包缓存到那里,下次 build 直接命中。

第一行 # syntax=docker/dockerfile:1.7 必须,开启高级 Dockerfile
语法。

效果:第一次 build 2.5 分钟;后续 npm install 5-10 秒。

3. apt cache mount

FROM ubuntu:24.04
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    rm -f /etc/apt/apt.conf.d/docker-clean \
 && apt update \
 && apt install -y --no-install-recommends \
        build-essential libpq-dev \
 && rm -rf /var/lib/apt/lists/*

第一次跑 apt-get update 30 秒;后续每次 build 这一步 < 5 秒。

4. pip / uv cache

FROM python:3.12-slim

# 用 uv 极快
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-install-project

COPY . .
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen

CMD ["uv", "run", "gunicorn", "myapp:app"]

uv 本身就快,加 cache mount 几乎是"第一次外,永远秒级"。

5. multi-stage build:让产物 image 不带 build dep

# === build stage ===
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN --mount=type=cache,target=/root/.npm \
    npm run build

# === runtime stage ===
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80

最终 image 只有 nginx + 静态文件,不含 node_modules / 源码。
小、安全、启动快。

6. 让 CI 跨 job 缓存 layer

GitHub Actions:

- uses: docker/setup-buildx-action@v3

- uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: ghcr.io/myorg/myapp:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max

type=gha 用 GitHub Actions 自带 cache 后端。
重复 build 跨 job 命中 cache。

GitLab CI:

build:
  image: docker:cli
  services: [docker:dind]
  script:
    - docker buildx create --use
    - docker buildx build
        --cache-from type=registry,ref=$CI_REGISTRY_IMAGE/cache
        --cache-to type=registry,ref=$CI_REGISTRY_IMAGE/cache,mode=max
        --push -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .

7. secret mount(不让 secret 进 image layer)

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci
docker build --secret id=npmrc,src=$HOME/.npmrc .

.npmrc 在 build 时挂入,build 完不留在 image 任何 layer。
比 ARG 安全(ARG 进 history 可被 docker history 看到)。

8. 并行 stage

FROM base AS deps
RUN install_deps

FROM base AS assets
RUN compile_assets

FROM runtime
COPY --from=deps /opt/deps /opt/deps
COPY --from=assets /opt/assets /opt/assets

BuildKit 自动并行无依赖的 stage(depsassets 同时跑)。

效果

按上面优化后我们的 build:

步骤 之前 之后
apt install 60s 5s
npm ci 150s 8s
pip install 90s 5s
webpack 120s 60s(业务代码改了仍要 build)
push 60s 10s(只 push 改了的 layer)
8m ~90s

CI iteration 速度提升 5x,开发体验改善明显。

调试 BuildKit

# 详细输出
docker build --progress plain -t myapp .

# 看 build cache
docker buildx du

# 清 cache(如果 cache 损坏 / 太大)
docker buildx prune
docker buildx prune --all

踩过的坑

  1. 没写 # syntax=... 注释:cache mount 等高级语法不识别,
    报 "unknown flag --mount"。第一行必加。

  2. sharing=locked 重要:多个 build 并发跑同一 cache → race condition
    损坏。locked 让 buildkit 串行访问。

  3. CI cache 太大被 evict:GitHub Actions cache 上限 10 GB / repo,
    mode=max 缓存所有 layer 容易超。改 mode=min 只缓存最终层,
    或定期清理。

  4. multi-arch build 慢--platform linux/amd64,linux/arm64
    QEMU 模拟另一架构很慢。改用 native runner(GitHub 提供 arm64 runner,
    或 self-host)。

  5. layer 顺序错:把变化频繁的 COPY . . 放在装依赖前 → 每次代码
    改都让依赖层失效。永远先 COPY package files 装依赖,再 COPY 代码。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。