はじめに
Docker公式のBest practices for writing Dockerfilesに
ADDよりもCOPYが望ましい(ADDは使わない方が良い)との記述があるのを見つけたのでその理由を確認してみました。
TL;DR
理由は以下の2つに分けることができます。
① Imageサイズの観点
② セキュリティの観点
そもそもCOPYとADDはどう違うのか
こちらの記事にて端的にまとめられていたので日本語訳してしまいます。
COPY - 明示的なコピー元とコピー先のファイルまたはディレクトリを指定して、ローカルファイルを再帰的にコピーします。 COPYでは、場所を宣言する必要があります。
ADD - ローカルファイルを再帰的にコピーし、存在しない場合は暗黙的にコピー先ディレクトリを作成し、アーカイブをコピー元としてローカルURLまたはリモートURLとして受け入れます。アーカイブはそれぞれコピー先ディレクトリに展開またはダウンロードされます。
COPY
はソース側のマシンにあるものをImageへコピーするだけというシンプルな機能であるのに対し、ADD
は対象が圧縮ファイルであればそれを展開してImageへ移し、リモート上のリソースも引っ張ってくる機能がついています。このCOPYに付与されたADDの機能自体が望ましくない原因になっています。
Imageサイズの観点
上記の公式ドキュメントに記載の内容です。
ADD
を使ってしまっているアンチパターンが以下になります。
FROM alpine:3.4
RUN apk --update add curl
ADD https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/Python-3.7.3.tar.xz -C /usr/src/things
これの何がいけないかというと、ADD https://...tar.xz /usr/src/things
の記述により結果的に不要なダウンロードされたディレクトリを持つImageレイヤーができてしまっている点です。
そうではなく、このようにしましょうということです。
FROM alpine:3.4
RUN apk --update add curl
RUN mkdir -p /usr/src/things \
&& curl -SL https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tar.xz \
| tar -xJC /usr/src/things
それぞれをビルドしてdocker images
とdocker history
で比較してみましょう。
$ docker images ng-sample
REPOSITORY TAG IMAGE ID CREATED SIZE
ng-sample latest 6f486d575040 15 minutes ago 103MB
$ docker history ng-sample
IMAGE CREATED CREATED BY SIZE COMMENT
6f486d575040 15 minutes ago /bin/sh -c tar -xJf /usr/src/things/Python-3… 79.3MB
7640da035c96 15 minutes ago /bin/sh -c #(nop) ADD da2574b8e3fb62fc8442c4… 17.1MB
15a01069edfd 26 minutes ago /bin/sh -c apk --update add curl 2.19MB
b7c5ffe56db7 3 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 3 months ago /bin/sh -c #(nop) ADD file:bcde2f5c6cea41907… 4.82MB
$ docker images ok-sample
REPOSITORY TAG IMAGE ID CREATED SIZE
ok-sample latest c88218b0c0cf 28 minutes ago 86.3MB
$ docker history ok-sample
IMAGE CREATED CREATED BY SIZE COMMENT
c88218b0c0cf 28 minutes ago /bin/sh -c mkdir -p /usr/src/things && c… 79.3MB
15a01069edfd 29 minutes ago /bin/sh -c apk --update add curl 2.19MB
b7c5ffe56db7 3 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 3 months ago /bin/sh -c #(nop) ADD file:bcde2f5c6cea41907… 4.82MB
ダメな方ではADD
で追加された圧縮ファイルを持ったレイヤー分サイズが大きくなってしまっています。改良版ではcurlした圧縮ファイルをパイプでtar
へつなげているのでImageには残りません。
セキュリティの観点
こちらの記事の内容の紹介になります。
上記のADD
の機能を利用者が知らずに使っている場合、ADD
はセキュリティのリスクが生じてしまいます。
- リモート上のリソースを自動で取りに行くのでダウンロード中、中間者攻撃の対象となりえる
- 圧縮ファイルを自動で展開するので、ZIP爆弾やZIP SLIP脆弱性攻撃の対象となりえる
ここで注意するべきは、上記のセキュリティリスクはCOPY
を使っている場合でも起こりえます。ただ、COPY
を使っていれば、
- 明示的にリモートリソースからダウンロード(wget, curlなど)をする
- 圧縮ファイルを展開(tarなど)する
ので対象リソースが安全かを検証する仕組みを組み込みやすいということです。
おまけ COPY
のコツ
これも上記公式ドキュメントに記載の話です。
Imageキャッシュをできるだけ利用できるようにすることを意識してDockerfileを記述しましょう。
COPY . /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/
2つの違いがImageキャッシュ利用にどう関わるか理解しておく必要があります。
RUN pip install --requirement /tmp/requirements.txt
はrequirements.txt
の差分のみに影響されて実行されるべきです。
ダメな方はそれ以外の.
以下の何かが変更されるたび、COPY . /tmp/
のレイヤーのImageコミットが変わるので、RUN pip install
は以前のキャッシュを利用することができず毎回ダウンロードしてインストールすることになってしまいます。