1. 基本構文

両者の構文はほぼ同じですが、動作には大きな違いがあります:

1
2
3
4
5
# COPY - 纯粹的文件复制,不做任何额外处理
COPY [--chown=<user>:<group>] <src>... <dest>

# ADD - 支持自动解压和远程 URL,但行为不可预测
ADD [--chown=<user>:<group>] <src>... <dest>

2. 圧縮ファイルの自動解凍

ADD はローカルの圧縮ファイルを自動的に解凍しますが、COPY は解凍しません:

1
2
3
4
5
6
7
# ADD 自动解压 .tar.gz
ADD app.tar.gz /opt/app/
# 结果:/opt/app/ 下是解压后的内容

# COPY 只复制文件本身
COPY app.tar.gz /opt/app/
# 结果:/opt/app/app.tar.gz 是原始压缩包

対応する圧縮形式:.tar、.tar.gz/.tgz、.tar.bz2、.tar.xz

圧縮ファイル自体を解凍せずにコピーしたい場合は、COPY を使う必要があります。

3. リモート URL のダウンロード

ADD は URL から直接ファイルをダウンロードできますが、COPY はサポートしていません:

1
2
3
4
5
# ADD 可以下载远程文件(但不推荐)
ADD https://example.com/app.tar.gz /opt/

# COPY 不支持 URL
COPY https://example.com/app.tar.gz /opt/  # 报错!

リモートファイルのダウンロードには RUN + curl で ADD を代替することを推奨します。ADD によるダウンロードはビルドキャッシュを利用できず、エラーハンドリングもなく、整合性の検証もできないためです。

1
2
3
4
# 推荐:RUN + curl
RUN curl -fsSL -o /tmp/app.tar.gz https://example.com/app.tar.gz \
    && tar -xzf /tmp/app.tar.gz -C /opt/ \
    && rm /tmp/app.tar.gz

4. ディレクトリの自動作成

どちらも存在しない中間ディレクトリを自動的に作成します:

1
2
3
4
5
6
# 如果 /etc/myapp/config/ 不存在,会自动创建
COPY app.conf /etc/myapp/config/
ADD app.conf /etc/myapp/config/

# 不需要手动 mkdir
# RUN mkdir -p /etc/myapp/config   ← 多余

5. ビルドキャッシュの動作

1
2
3
4
5
# COPY - 严格匹配文件内容,内容变了才缓存失效
COPY config/app.conf /etc/app/

# ADD - 压缩文件内容相同时缓存仍然有效
ADD app.tar.gz /opt/app/
シナリオ COPY ADD
ファイル内容の変化 キャッシュ無効 キャッシュ無効
ファイルメタデータの変化 キャッシュ無効 キャッシュ無効
圧縮ファイルの内容が同じ N/A キャッシュ有効

6. 機能比較

特性 COPY ADD
ローカルファイルのコピー ✅ ✅
ディレクトリのコピー ✅ ✅
ワイルドカードのサポート ✅ ✅
ディレクトリの自動作成 ✅ ✅
圧縮ファイルの自動解凍 ❌ ✅
リモート URL のサポート ❌ ✅
意味の明確さ ✅ 高 ❌ 低
推奨使用 ✅ 第一選択 ⚠️ 特定シナリオ

7. 使用シナリオ

COPY が適しているケース:

1
2
3
4
5
6
7
8
9
# 复制配置文件
COPY nginx.conf /etc/nginx/nginx.conf

# 复制应用代码
COPY src/ /app/src/
COPY package.json /app/

# 多阶段构建中复制二进制
COPY --from=builder /app/build/app /usr/local/bin/

ADD は自動解凍が必要なシナリオのみに適しています:

1
2
3
4
5
# 合理使用 ADD
ADD node-v18.17.0-linux-x64.tar.gz /usr/local/

# 普通文件不要用 ADD
ADD app.conf /etc/app/  # ← 用 COPY 代替

8. ベストプラクティス

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 规则 1:默认用 COPY
COPY requirements.txt /app/          # ✅
ADD requirements.txt /app/           # ❌ 没必要

# 规则 2:只在需要解压时用 ADD
ADD node-v18.tar.gz /usr/local/      # ✅ 需要解压

# 规则 3:远程文件用 RUN + curl
RUN curl -fsSL -o /tmp/app.tar.gz https://example.com/app.tar.gz \
    && tar -xzf /tmp/app.tar.gz -C /opt/ \
    && rm /tmp/app.tar.gz            # ✅
ADD https://example.com/app.tar.gz /opt/  # ❌

# 规则 4:用 .dockerignore 减少上下文
# .dockerignore
# .git
# node_modules
# *.md
# .env

# 规则 5:合理安排 COPY 顺序利用缓存
COPY package.json package-lock.json ./  # 先复制依赖(变化少)
RUN npm ci --production
COPY . .                                # 再复制代码(变化多)

9. よくある落とし穴

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 陷阱 1:意外的自动解压
ADD app.tar.gz /opt/app/
# 你以为复制了文件,实际解压成了目录

# 陷阱 2:远程文件无法缓存
ADD https://github.com/user/repo/releases/download/v1.0/app.tar.gz /tmp/
# 每次构建都重新下载

# 陷阱 3:权限问题
COPY --chown=app:app src/ /app/src/    # 指定所有者

10. 選択の判断基準

1
2
3
4
5
6
7
需要复制文件?
├── 本地压缩文件且需要解压?
│   └── 用 ADD
├── 本地普通文件?
│   └── 用 COPY
└── 远程文件?
    └── 用 RUN + curl