## 7.2 COPY 复制文件

### 7.2.1 基本语法

```docker
COPY [选项] <源路径>... <目标路径>
COPY [选项] ["<源路径1>", "<源路径2>", ... "<目标路径>"]
```

`COPY` 指令将构建上下文中的文件或目录复制到镜像内。

---

### 7.2.2 基本用法

#### 复制单个文件

```docker
## 复制文件到指定目录

COPY package.json /app/

## 复制文件并重命名

COPY config.json /app/settings.json
```

#### 复制多个文件

```docker
## 复制多个指定文件

COPY package.json package-lock.json /app/

## 使用通配符

COPY *.json /app/
COPY src/*.js /app/src/
```

#### 复制目录

```docker
## 复制整个目录的内容（不是目录本身）

COPY src/ /app/src/
```

> ⚠️ **注意**：复制目录时，复制的是目录的 **内容**，不包含目录本身。

```bash
构建上下文：              镜像内：
src/                     /app/src/
├── index.js      →      ├── index.js
└── utils.js             └── utils.js
```

---

### 7.2.3 通配符规则

COPY 支持 Go 的 `filepath.Match` 通配符规则：

| 通配符 | 说明 | 示例 |
|--------|------|------|
| `*` | 匹配任意字符序列 | `*.json` |
| `?` | 匹配单个字符 | `config?.json` |
| `[abc]` | 匹配括号内任一字符 | `[abc].txt` |
| `[a-z]` | 匹配范围内字符 | `file[0-9].txt` |

```docker
COPY hom* /mydir/       # home.txt, homework.md 等
COPY hom?.txt /mydir/   # home.txt, homy.txt 等
COPY app[0-9].js /app/  # app0.js ~ app9.js
```

---

### 7.2.4 目标路径

#### 绝对路径

```docker
COPY app.js /usr/src/app/
```

#### 相对路径 (基于 WORKDIR)

```docker
WORKDIR /app
COPY package.json ./        # 复制到 /app/package.json
COPY src/ ./src/            # 复制到 /app/src/
```

#### 自动创建目录

如果目标目录不存在，Docker 会自动创建：

```docker
## /app/config/ 不存在也会自动创建

COPY settings.json /app/config/
```

---

### 7.2.5 修改文件所有者

使用 `--chown` 选项设置文件的用户和组：

```docker
## 使用用户名和组名

COPY --chown=node:node package.json /app/

## 使用 UID 和 GID

COPY --chown=1000:1000 . /app/

## 只指定用户

COPY --chown=node . /app/
```

> 💡 结合 `USER` 指令使用，确保应用以非 root 用户运行。

---

### 7.2.6 保留文件元数据

COPY 会保留源文件的元数据：

- 读、写、执行权限
- 修改时间

这对于脚本文件特别重要：

```docker
## start.sh 的可执行权限会被保留

COPY start.sh /app/
```

---

### 7.2.7 COPY vs ADD

| 特性 | COPY | ADD |
|------|------|-----|
| 复制本地文件 | ✅ | ✅ |
| 自动解压 tar | ❌ | ✅ |
| 支持 URL | ❌ | ✅ (不推荐)|
| 推荐程度 | ✅ **推荐** | ⚠️ 特殊场景使用 |

```docker
## 推荐：使用 COPY

COPY app.tar.gz /app/
RUN tar -xzf /app/app.tar.gz

## ADD 会自动解压（行为不明显，不推荐）

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

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

---

### 7.2.8 多阶段构建中的 COPY

#### 从其他构建阶段复制

```docker
## 构建阶段

FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

## 生产阶段

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
```

#### 使用 --link 优化缓存

```docker
## 使用 --link 后，文件以独立层添加，不依赖前序指令

COPY --link --from=builder /app/dist /usr/share/nginx/html
```

`--link` 的优势：

- 更高效利用构建缓存
- 并行化构建过程
- 加速多阶段构建

---

### 7.2.9 dockerignore

使用 `.dockerignore` 排除不需要复制的文件：

```gitignore
## .dockerignore

node_modules
.git
.env
*.log
Dockerfile
.dockerignore
```

这可以：

- 减小构建上下文大小
- 加速构建
- 避免复制敏感文件

---

### 7.2.10 最佳实践

#### 1. 利用缓存，先复制依赖文件

```docker
## ✅ 好：先复制依赖定义，再安装，最后复制代码

COPY package.json package-lock.json ./
RUN npm install
COPY . .

## ❌ 差：一次性复制所有文件，代码变更会导致重新 npm install

COPY . .
RUN npm install
```

#### 2. 使用 .dockerignore

```docker
## 确保 node_modules 不被复制

COPY . .
## .dockerignore 中应包含 node_modules

...
```

#### 3. 明确复制路径

```docker
## ✅ 好：明确的路径

COPY src/ /app/src/
COPY package.json /app/

## ❌ 差：过于宽泛

COPY . .
```

---
