2026年6月28日

2026年6月28日

DockerfileのCOPYとADDの違い|使い分けの基準とコピーでつまずきやすい挙動

Dockerfileの COPY と ADD はどちらもファイルをイメージに入れる命令ですが、COPY は「ビルドコンテキスト(またはほかのステージ)からそのままコピーするだけ」、ADD はそれに加えて「ローカルのtarアーカイブの自動展開」と「URLやGitリポジトリからの取得」を行います。基本は COPY を使い、ADD はそれらの追加機能が本当に必要なときだけ使う、というのがDocker公式のベストプラクティスでも示されている考え方です。この記事では、両者の違いと選び方に加えて、ディレクトリのコピー、末尾のスラッシュ、所有者の指定、.dockerignore など、実際につまずきやすい点を説明します。

内容はBuildKitを使う現在のDocker(2026年10月現在)を前提にしています。

違いの要点

COPY ADD
ビルドコンテキストのファイル コピーできる コピーできる
ほかのステージから(--from) できる できない
ローカルのtarファイル そのままコピー 展開して配置
URLからの取得 できない できる(展開はしない)
Gitリポジトリの取得 できない できる(BuildKit)

ADD の便利な機能は、裏を返すと「書いた本人の意図と違う動きをすることがある」という意味でもあります。たとえば ADD backup.tar.gz /data/ は、ファイルを置くつもりでも中身が展開されます。逆にURLから取得したtarは展開されません。読み手が挙動を推測しやすいのは COPY なので、特別な理由がなければ COPY を選びます。

COPYの基本と注意点

FROM wordpress:php8.3-apache
COPY --chown=www-data:www-data ./mytheme/ /usr/src/wordpress/wp-content/themes/mytheme/

ディレクトリは「中身」がコピーされる

COPY src/ /app/ と書くと、src ディレクトリそのものではなく、その中身が /app/ に入ります。/app/src/ になると思っているとパスがずれるので、コピー先にディレクトリ名まで書くのが安全です。

末尾のスラッシュの意味

コピー先が / で終わっていればディレクトリとして扱われ、終わっていなければファイル名として扱われます。複数のファイルを1行でコピーする場合、コピー先は / で終わるディレクトリにしなければなりません。

COPY composer.json composer.lock /var/www/app/

ビルドコンテキストの外は参照できない

COPY ../shared/config.php /app/ のように、docker build に渡したディレクトリ(ビルドコンテキスト)より上のファイルは指定できません。必要なファイルはコンテキストの中に置くか、ビルドコンテキストの位置を親ディレクトリに変えます。

所有者と権限

COPY でコピーしたファイルの所有者は、指定しなければroot(UID 0)になります。WordPressのようにWebサーバーのユーザーが書き込む必要がある場合は、--chown で指定します。後から RUN chown -R を実行すると、ファイルがもう一度レイヤーに保存されてイメージが大きくなるため、コピーの時点で指定するほうが効率的です。

権限は --chmod=644 のように指定できます(BuildKitが必要です)。

.dockerignoreで余計なものを送らない

COPY . . は、ビルドコンテキストにあるものを丸ごとコピーします。.git、node_modules、.env、ローカルのアップロード画像などがあると、イメージが重くなるうえ、秘密情報がイメージに入る原因になります。プロジェクト直下に .dockerignore を置いて除外します。

.git
node_modules
.env
*.log
wp-content/uploads

.env を除外し忘れたイメージを公開レジストリに置くと、中身を誰でも取り出せます。秘密情報を COPY で入れないことは、ARGやENVに入れないことと同じくらい大事です。

マルチステージビルドでのCOPY --from

COPY --from=<ステージ名> で、前のステージで作ったファイルだけを最終イメージに持ってこられます。テーマのCSSやJavaScriptをNode.jsでビルドし、成果物だけをWordPressのイメージに入れる例です。

FROM node:20 AS assets
WORKDIR /src
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM wordpress:php8.3-apache
COPY --chown=www-data:www-data ./mytheme/ /usr/src/wordpress/wp-content/themes/mytheme/
COPY --from=assets --chown=www-data:www-data /src/dist/ /usr/src/wordpress/wp-content/themes/mytheme/assets/

テーマのPHPファイルはビルドコンテキストから、ビルド済みのCSS・JavaScriptは assets ステージから、それぞれコピーしています。Node.jsや node_modules は最終イメージに入りません。

package.json を先にコピーして npm ci を実行しているのは、ソースだけ変えたときに依存パッケージのインストールをキャッシュから再利用するためです。変わりにくいファイルを先に、変わりやすいファイルを後にコピーすると、ビルド時間を短くできます。

なお、公式のWordPressイメージでは、起動時に /usr/src/wordpress の内容を /var/www/html に展開する仕組みになっています。テーマやプラグインを焼き込む場合のコピー先は、使っているイメージの説明ページで確認してください。

ADDを使う場面

ローカルのtarを展開して配置する

ADD vendor-bundle.tar.gz /opt/vendor/

gzipなどで圧縮したtarがビルドコンテキストにあり、展開した状態で置きたいときは ADD が簡潔です。COPY で入れて RUN tar で展開すると、圧縮ファイルと展開後のファイルの両方がレイヤーに残ってしまいます。

URLから取得する場合はチェックサムを付ける

ADD にURLを書くと、ビルド時にダウンロードします。取得したファイルが改ざんされていないか確かめるため、--checksum でハッシュ値を指定できます(比較的新しいDockerfileの構文で使える機能です)。

# syntax=docker/dockerfile:1
ADD --checksum=sha256:<配布元が公開しているハッシュ値> https://example.com/tool-1.2.3.tar.gz /tmp/

ハッシュ値は配布元が公開しているものを使います。URLからの取得は、キャッシュの扱いが分かりにくかったり、ダウンロード元の障害でビルドが止まったりする面もあります。ダウンロードと展開と不要ファイルの削除を1つの層で済ませたい場合は、RUN curl ... && tar ... && rm ... のように書くほうが、イメージを小さく保てることがあります。

Gitリポジトリから取得する

BuildKitでは ADD https://github.com/<組織>/<リポジトリ>.git#<タグ> /src のようにGitリポジトリを取得できます。ブランチ名ではなくタグやコミットを指定すると、ビルドの再現性が保てます。非公開リポジトリの認証はビルド時のシークレットやSSHの転送で渡し、Dockerfileにトークンを書かないようにします。

どちらを使うか迷ったとき

  1. ほかのステージからのコピー → COPY --from
  2. ビルドコンテキストのファイルをそのまま入れる → COPY
  3. ローカルのtarを展開して入れたい → ADD
  4. URLから取得したい → ADD --checksum、または RUN curl で取得・検証・削除までまとめる

迷ったら COPY にして、追加機能が必要になったときだけ ADD に替える、という順番で考えると、意図しない展開や取得を避けられます。

まとめ

  • COPY はコピーだけ、ADD はtarの自動展開とURL・Gitからの取得も行う。基本は COPY。
  • ディレクトリは中身がコピーされる。複数ファイルのコピー先は / で終わるディレクトリにする。
  • 所有者は --chown でコピー時に指定すると、イメージが大きくならない。
  • .dockerignore で .git や .env を除外し、秘密情報をイメージに入れない。
  • URLから取るなら --checksum を付けるか、RUN curl で検証まで行う。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲