Dockerfile ADD or COPY

今天來學習一下Dockerfile中ADD與COPY的區別,這兩個指令從字面上看意思非常相近所以很常搞混
先來看一下Docker最佳實踐

文中說明雖然兩個功能相近,但是最好優先使用COPY

  • COPY 只支援將本地文件複製到container內的功能
  • ADD 同樣能做到本地複製,並且多了自動tar解包與URL遠端檔案下載功能

並且建議有需要用到自動解包tar時才使用ADD

那麼我們來做一下幾個測試

mkdir hello && cd $_
touch hello.txt
touch hello2.txt
chmod 666 hello2.txt
tar zcvf hello2.tar.gz hello2.txt
apk add curl
curl https://github.com/git/git/archive/refs/tags/v2.39.2.tar.gz -o v2.39.2.curl.tar.gz
vi Dockerfile

貼上以下內容

FROM ubuntu:latest
RUN mkdir -p /hello
COPY hello.txt /hello
ADD v2.39.2.curl.tar.gz /hello/
ADD https://github.com/git/git/archive/refs/tags/v2.39.2.tar.gz /hello/
ADD hello2.tar.gz /hello

建立Image

docker build -t imagetest .

可以看一下第五步驟:此處將去github下載一份tar.gz
這個部分也是ADD的缺點之一,就是沒辦法使用cache
也就是說每次build時都需要重新下載,會大大影響build的速度

[node1] (local) root@192.168.0.18 ~/hello
$ docker build -t imagetest .
Sending build context to Docker daemon  4.608kB
Step 1/6 : FROM ubuntu:latest
latest: Pulling from library/ubuntu
677076032cca: Pull complete 
Digest: sha256:9a0bdde4188b896a372804be2384015e90e3f84906b750c1a53539b585fbbe7f
Status: Downloaded newer image for ubuntu:latest
 ---> 58db3edaf2be
Step 2/6 : RUN mkdir -p /hello
 ---> Running in 025e153c6cbb
Removing intermediate container 025e153c6cbb
 ---> 054a0e9ff22d
Step 3/6 : COPY hello.txt /hello
 ---> 4e7f54ac7c7d
Step 4/6 : ADD v2.39.2.curl.tar.gz /hello
 ---> a58d4c534ac4
Step 5/6 : ADD https://github.com/git/git/archive/refs/tags/v2.39.2.tar.gz /hello/
Downloading  10.58MB

 ---> fe984824a91a
Step 6/6 : ADD hello2.tar.gz /hello
 ---> 1eacc3a644dc
Successfully built 1eacc3a644dc
Successfully tagged imagetest:latest

再重新build一次看一下第四步驟,這邊有趣的是我一開始就先把這個檔案下載下來,在使用ADD指令將v2.39.2.curl.tar.gz複製進container裡面

發現能夠正常的使用cache,而且功能也跟COPY指令一樣

反而第五步驟還在重新下載

[node1] (local) root@192.168.0.18 ~/hello
$ docker build -t imagetest .
Sending build context to Docker daemon  4.608kB
Step 1/6 : FROM ubuntu:latest
 ---> 58db3edaf2be
Step 2/6 : RUN mkdir -p /hello
 ---> Using cache
 ---> 054a0e9ff22d
Step 3/6 : COPY hello.txt /hello
 ---> Using cache
 ---> 4e7f54ac7c7d
Step 4/6 : ADD v2.39.2.curl.tar.gz /hello
 ---> Using cache
 ---> a58d4c534ac4
Step 5/6 : ADD https://github.com/git/git/archive/refs/tags/v2.39.2.tar.gz /hello/
Downloading  10.58MB

 ---> Using cache
 ---> fe984824a91a
Step 6/6 : ADD hello2.tar.gz /hello
 ---> Using cache
 ---> 1eacc3a644dc
Successfully built 1eacc3a644dc
Successfully tagged imagetest:latest

接下來運行起一個container,並且進入到container內部環境

docker run -dit imagetest 
docker attach ec79

這邊有個特別的地方就是hello2.txt
明明Dockerfile第六步驟是複製hello2.tar.gz
但是結果是出現hello2.txt
這就是之前提到的ADD指令的自動解包功能
但是v2.39.2.curl.tar.gz也是使用ADD指令阿
怎麼沒有自動解壓呢,這個也是ADD指令的內部規則
如果是網路下載的檔案並不會自動解包,還需要搭配RUN指令 並且會將權限設定成600,要更改權限也一樣要搭配RUN指令 所以這邊v2.39.2.tar.gz權限才會是-rw-------

root@ec792359f99f:/hello# ls -l
total 10332
-rw-r--r-- 1 root root        0 Feb 19 11:35 hello.txt
-rw-rw-rw- 1 root root        0 Feb 19 11:35 hello2.txt
-rw-r--r-- 1 root root        0 Feb 19 11:35 v2.39.2.curl.tar.gz
-rw------- 1 root root 10579815 Jan  1  1970 v2.39.2.tar.gz

這邊先整一下ADD的限制

  • 網路下載的檔案並不會自動解包,下載完成要搭配RUN指令進行解包
  • 解包完成的tar.gz檔案,要使用RUN指令將它移除不然會影響image的大小
  • 網路下載的檔案會將權限設定成600,要更改權限也一樣要搭配RUN指令來修改
  • 每次build時都需要重新下載,沒法使用cache

看到這些限制,你就會覺得乾脆就用curl搭配COPY就好了,反正最後還是要寫解包的指令還要調整權限
事實上Docker也知道這個問題,所以在最佳實踐裡面
這個段落的最後,官方也是推薦使用curl搭配COPY
除非有使用到自動解包的功能在使用ADD其他場合使用COPY是最佳的作法
最後因為RUN, COPY, ADD指令,Docker會幫我們創建一個layers
詳細可以看之前的文章Docker Certified Associate(DCA)認證考試學習-Docker Image Layer

所以可以將一些步驟寫在同一RUN底下,這樣可以大大減少layer的產生

RUN mkdir -p /usr/src/things \
    && curl -SL https://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

但還是要注意經常修改的檔案最好還是自己獨立一個image,盡量多加利用cache的機制


Summary

今天我們學習了ADDCOPY這兩個指令,以及他們的使用場合
依結論來看ADD使用的場合真的挺少的,大部分的範例也都只有使用COPY 畢竟沒辦法使用cache還是最大的問題