起因
我们的 Node + Python 微服务 CI 每次 build:
apt install系统依赖:1 分钟npm ci装 node_modules:2.5 分钟pip install装 Python 依赖:1.5 分钟- webpack 编译:2 分钟
- 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(deps 和 assets 同时跑)。
效果
按上面优化后我们的 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
踩过的坑
-
没写
# syntax=...注释:cache mount 等高级语法不识别,
报 "unknown flag --mount"。第一行必加。 -
sharing=locked重要:多个 build 并发跑同一 cache → race condition
损坏。locked让 buildkit 串行访问。 -
CI cache 太大被 evict:GitHub Actions cache 上限 10 GB / repo,
mode=max缓存所有 layer 容易超。改mode=min只缓存最终层,
或定期清理。 -
multi-arch build 慢:
--platform linux/amd64,linux/arm64时
QEMU 模拟另一架构很慢。改用 native runner(GitHub 提供 arm64 runner,
或 self-host)。 -
layer 顺序错:把变化频繁的
COPY . .放在装依赖前 → 每次代码
改都让依赖层失效。永远先 COPY package files 装依赖,再 COPY 代码。
登录后参与评论。