# 深度解密 Docker BuildKit：如何利用 --mount=type=cache 加速你的构建流水线


在 Docker 构建过程中，最耗时的环节通常是下载依赖（如 `npm install`、`pnpm install`、`go mod download`）。传统的 `COPY` 方式会导致每次代码变动都重新下载，而 BuildKit 引入的 `--mount=type=cache` 彻底改变了这一现状。 （和 docker 层缓存不是一个级别的概念）

## 一、核心概念：什么是 type=cache？

在 Dockerfile 的 `RUN` 指令中，`--mount=type=cache` 允许你挂载一个持久化的、跨构建共享的缓存目录。

### 1. 底层原理：抽屉理论

想象 Docker 引擎内部有一个"物理硬盘柜"，每个 `id` 就是一个"抽屉"：

- **挂载（Mounting）**：构建开始时，Docker 把对应的抽屉插进容器的 `target` 目录。
- **执行（Execution）**：程序（如 `pnpm`）向该目录写入数据（存入依赖包）。
- **卸载（Unmounting）**：构建结束，Docker 拔出抽屉，且不会将其内容打包进镜像层。

### 2. 它与 COPY 的区别

| 特性 | `COPY` / `ADD` | `--mount=type=cache` |
|------|----------------|----------------------|
| 存储位置 | 永久写入镜像层 | 存放在 Docker 引擎管理的独立空间 |
| 镜像体积 | 增加镜像体积 | 不占用镜像体积 |
| 共享性 | 仅当前层可用 | 跨项目、跨构建、多次共享 |
| 宿主机可见性 | 包含在导出的镜像中 | 宿主机不可见（受 Docker 保护） |

## 二、实战配置：以 pnpm 为例

这是目前最优雅的 Dockerfile 片段，通过绑定 `package.json` 和挂载缓存实现极速构建：

```dockerfile
# 使用 BuildKit 特性
RUN --mount=type=bind,source=package.json,target=package.json \
    --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
    --mount=type=cache,id=pnpm-cache,target=/root/.local/share/pnpm/store \
    pnpm install --frozen-lockfile
```

**参数细节拆解：**

- `type=bind`：将宿主机的配置文件"借"给容器用，用完即还，不留痕迹。
- `id=pnpm-cache`：这是缓存块的唯一标识。相同 ID 的构建任务会共享同一个"抽屉"。
- `target=/root/.local/share/pnpm/store`：**关键点！** 必须指向工具（`pnpm`/`npm`）在容器内默认存放缓存的路径。

## 三、深度 Q&A：你想知道的细节都在这里

**Q1：多个项目共用一个 `id`，会有冲突吗？**

不会损坏文件，但会产生"依赖并集"。由于 pnpm 是内容寻址（基于哈希存储），项目 A 的 React 和项目 B 的 Vue 会平安无事地共存在同一个 Store 中。这反而能实现"一次下载，处处复用"的效果。

**Q2：为什么我在宿主机磁盘上找不到这个缓存目录？**

出于安全和隔离考虑，Docker 将这些缓存存储在引擎内部（如 `/var/lib/docker/buildkit` 下的加密目录）。你不能直接通过 `ls` 看到它，只能通过 `docker builder du` 管理。

**Q3：如何确认缓存是否真的生效了？**

- **看日志**：如果生效，`pnpm install` 会提示 `Packages are up to date`，且耗时从分钟级降至秒级。
- **看大小**：运行 `docker builder du`，类型为 `exec.cachemount` 的条目就是你的缓存块。

**Q4：缓存坏了或者想清理怎么办？**

- **物理清理**：`docker builder prune --filter "label=buildkit.mount.id=pnpm-cache"`
- **逻辑刷新**：在 Dockerfile 中将 `id` 改为 `pnpm-cache-v2`，Docker 会立即启用全新的空缓存。

## 四、注意事项与最佳实践（避坑指南）

- **路径匹配**：确保 `target` 路径与镜像内的用户家目录匹配（如 `root` 用户通常是 `/root/...`，普通用户可能是 `/home/node/...`）。
- **避免绝对路径错误**：不要在 `target` 里写宿主机的绝对路径（如 `/Volumes/User/cache`），那会导致挂载失败或指向错误的容器内部位置。
- **权限问题**：如果你在 Dockerfile 中切换了 `USER`，请确保该用户对 `target` 目录有读写权限。

