## 7.3 ADD 更高级的复制文件

### 何时使用 ADD？何时用 COPY？

在开始前，让我们直言不讳：**在大多数情况下，你应该使用 COPY，而不是 ADD**。

`ADD` 在 `COPY` 基础上增加了两个额外功能，但这些功能往往引入复杂性而非便利：

1. 自动解压 tar 压缩包（有时你想复制一个 .tar.gz 本身，而 ADD 会意外地解压它）
2. 支持从 URL 下载文件（这个功能由于网络不稳定已被广泛认为是反模式）

**实践中的建议**：除非你明确需要自动解压功能（比如官方基础镜像构建根文件系统），否则始终使用 COPY。原因很简单——显式优于隐式。你的 Dockerfile 在 6 个月后被接手维护时，清晰的意图会让团队少走很多弯路。

### 7.3.1 基本语法

```docker
ADD [选项] <源路径>... <目标路径>
ADD [选项] ["<源路径>", ... "<目标路径>"]
```
`ADD` 在 `COPY` 基础上增加了两个功能：

1. 自动解压 tar 压缩包
2. 支持从 URL 下载文件 (不推荐)

---

### 7.3.2 ADD vs COPY 详细对比

| 特性 | COPY | ADD |
|------|------|-----|
| 复制本地文件 | ✅ | ✅ |
| 自动解压 tar | ❌ | ✅ |
| 支持 URL | ❌ | ✅ (不推荐)|
| 行为可预测性 | ✅ 高 | ⚠️ 低 |
| 推荐程度 | ✅ **优先使用** | 仅解压场景 |

> **笔者建议**：除非需要自动解压 tar 文件，否则始终使用 COPY。明确的行为比隐式的魔法更好。

---

### 7.3.3 自动解压功能

#### 基本用法：自动解压本地 tar

```docker
## 自动解压 tar.gz 到目标目录

ADD app.tar.gz /app/
```
ADD 会识别并解压以下格式：

- `.tar`
- `.tar.gz` / `.tgz`
- `.tar.bz2` / `.tbz2`
- `.tar.xz` / `.txz`

#### 实际应用

官方基础镜像通常使用 ADD 解压根文件系统：

```docker
FROM scratch
ADD ubuntu-noble-core-cloudimg-amd64-root.tar.gz /
```

#### 解压过程

```bash
ADD app.tar.gz /app/
        │
        ├─ 识别 .tar.gz 格式
        ├─ 自动解压
        └─ 内容放入 /app/

app.tar.gz 包含：        /app/ 目录结果：
├── src/                 ├── src/
│   └── main.py          │   └── main.py
└── config.json          └── config.json
```
---

### 7.3.4 URL 下载功能：不推荐

#### 基本用法

```docker
## 从 URL 下载文件

ADD https://example.com/app.zip /app/app.zip
```

#### 为什么不推荐

| 问题 | 说明 |
|------|------|
| 权限固定 | 下载的文件权限为 600，通常需要额外 RUN 修改 |
| 不会解压 | URL 下载的压缩包不会自动解压 |
| 缓存问题 | URL 内容变化时不会重新下载 |
| 层数增加 | 需要额外 RUN 清理 |

#### 推荐替代方案

```docker
## ❌ 不推荐：使用 ADD 下载

ADD https://example.com/app.tar.gz /tmp/
RUN tar -xzf /tmp/app.tar.gz -C /app && rm /tmp/app.tar.gz

## ✅ 推荐：使用 RUN + curl

RUN curl -fsSL https://example.com/app.tar.gz | tar -xz -C /app
```
优势：

- 一条 RUN 完成下载、解压、清理
- 减少镜像层数
- 更清晰的构建意图

---

### 7.3.5 修改文件所有者

```docker
ADD --chown=node:node app.tar.gz /app/
ADD --chown=1000:1000 files/ /app/
```
---

### 7.3.6 何时使用 ADD

#### ✅ 适合使用 ADD

```docker
## 解压本地 tar 文件

FROM scratch
ADD rootfs.tar.gz /

## 解压应用包

ADD dist.tar.gz /app/
```

#### ❌ 不适合使用 ADD

```docker
## 复制普通文件（用 COPY）

ADD package.json /app/          # ❌
COPY package.json /app/         # ✅

## 下载文件（用 RUN + curl）

ADD https://example.com/file /  # ❌
RUN curl -fsSL ... -o /file     # ✅

## 需要保留 tar 不解压（用 COPY）

ADD archive.tar.gz /archives/   # ❌ 会解压
COPY archive.tar.gz /archives/  # ✅ 保持原样
```
---

### 7.3.7 缓存行为

ADD 可能导致构建缓存失效：

```docker
## 如果 app.tar.gz 内容变化，此层及后续层都需重建

ADD app.tar.gz /app/
RUN npm install
```
**优化建议**：

```docker
## 先复制依赖文件

COPY package*.json /app/
RUN npm install

## 再添加应用代码

ADD app.tar.gz /app/
```
---

### 7.3.8 最佳实践

#### 1. 默认使用 COPY

```docker
## ✅ 大多数场景使用 COPY

COPY . /app/
```

#### 2. 仅在需要解压时使用 ADD

```docker
## ✅ 自动解压场景

ADD app.tar.gz /app/
```

#### 3. 不要用 ADD 下载文件

```docker
## ❌ 避免

ADD https://example.com/file.tar.gz /tmp/

## ✅ 推荐

RUN curl -fsSL https://example.com/file.tar.gz | tar -xz -C /app
```

#### 4. 解压后清理

```docker
## 如果需要控制解压过程

COPY app.tar.gz /tmp/
RUN tar -xzf /tmp/app.tar.gz -C /app && \
    rm /tmp/app.tar.gz
```
---
