## 7.13 ONBUILD 为他人作嫁衣裳

### 7.13.1 基本语法

```docker
ONBUILD <其它指令>
```

`ONBUILD` 是一个特殊的指令，它后面跟的是其它指令 (如 `RUN`，`COPY` 等)，这些指令 **在当前镜像构建时不会执行**，只有当以当前镜像为基础镜像去构建下一级镜像时才会被执行。

---

### 7.13.2 为什么需要 ONBUILD

`ONBUILD` 主要用于制作 **语言栈基础镜像** 或 **框架基础镜像**。

#### 场景：维护 Node.js 项目

假设你有多个 Node.js 项目，它们的构建流程都一样：

1. 创建目录
2. 复制 `package.json`
3. 执行 `npm install`
4. 复制源码
5. 启动应用

如果不使用 `ONBUILD`，每个项目的 Dockerfile 都要重复这些步骤，且通过 `COPY` 复制文件时，基础镜像无法预知子项目的文件名。

#### 使用 ONBUILD 的解决方案

**基础镜像 (my-node-base)**：

```docker
FROM node:20-alpine
WORKDIR /app

## 这些指令将在子镜像构建时执行

ONBUILD COPY package*.json ./
ONBUILD RUN npm install
ONBUILD COPY . .

CMD ["npm", "start"]
```

**子项目 Dockerfile**：

```docker
FROM my-node-base
## 只需要一行！

## 构建时会自动执行 COPY 和 RUN

...
```

---

### 7.13.3 执行机制

```bash
基础镜像构建：
Dockerfile (含 ONBUILD) ──build──> 基础镜像 (记录了 ONBUILD 触发器)
                                    (指令未执行)

子镜像构建：
FROM 基础镜像 ──build──> 读取基础镜像触发器 ──> 执行触发器指令 ──> 继续执行子 Dockerfile
```

---

### 7.13.4 常见使用场景

#### 1. 自动处理依赖安装

```docker
## Python 基础镜像

ONBUILD COPY requirements.txt ./
ONBUILD RUN pip install -r requirements.txt
```

#### 2. 自动编译代码

```docker
## Go 基础镜像

ONBUILD COPY . .
ONBUILD RUN go build -o app main.go
```

#### 3. 处理静态资源

```docker
## Nginx 静态网站基础镜像

ONBUILD COPY dist/ /usr/share/nginx/html/
```

---

### 7.13.5 注意事项

#### 1. 继承性限制

`ONBUILD` 指令 **只会继承一次**。

- 镜像 A (含 ONBUILD)
- 镜像 B (FROM A) -> 触发 ONBUILD
- 镜像 C (FROM B) -> **不会** 再次触发 ONBUILD

#### 2. 构建上下文

子镜像构建时，`ONBUILD COPY . .` 中的 `.` 指的是 **子项目** 的构建上下文，而不是基础镜像的上下文。

#### 3. 不允许级联

`ONBUILD ONBUILD` 是非法的。你不能写 `ONBUILD ONBUILD COPY ...`。

#### 4. 可能会导致构建失败

由于 `ONBUILD` 实际上是在子镜像中执行指令，如果子项目的上下文不满足要求 (例如缺少 `package.json`)，会导致子镜像构建失败，且错误信息可能比较隐晦。

---

### 7.13.6 最佳实践

#### 1. 命名规范

建议在镜像标签中添加 `-onbuild` 后缀，明确告知使用者该镜像包含触发器。

```bash
node:20-onbuild
python:3.12-onbuild
```

#### 2. 避免执行耗时操作

尽量不要在 `ONBUILD` 中执行过于耗时或不确定的操作 (如更新系统软件)，这会让子镜像构建变得缓慢且不可控。

#### 3. 清理工作

如果 `ONBUILD` 指令产生了临时文件，最好在同一个指令链中清理，或者提供机制让子镜像清理。

---
