Dockerfile 中提供了兩個很是類似的命令 COPY 和 ADD,本文嘗試解釋這兩個命令的基本功能,以及其異同點,而後總結其各自適合的應用場景。html
在使用 docker build 命令經過 Dockerfile 建立鏡像時,會產生一個 build 上下文(context)。所謂的 build 上下文就是 docker build 命令的 PATH 或 URL 指定的路徑中的文件的集合。在鏡像 build 過程當中能夠引用上下文中的任何文件,好比咱們要介紹的 COPY 和 ADD 命令,就能夠引用上下文中的文件。mysql
默認狀況下 docker build -t testx . 命令中的 . 表示 build 上下文爲當前目錄。固然咱們能夠指定一個目錄做爲上下文,好比下面的命令:linux
$ docker build -t testx /home/nick/hc
咱們指定 /home/nick/hc 目錄爲 build 上下文,默認狀況下 docker 會使用在上下文的根目錄下找到的 Dockerfile 文件。git
COPY 和 ADD 命令不能拷貝上下文以外的本地文件
對於 COPY 和 ADD 命令來講,若是要把本地的文件拷貝到鏡像中,那麼本地的文件必須是在上下文目錄中的文件。其實這一點很好解釋,由於在執行 build 命令時,docker 客戶端會把上下文中的全部文件發送給 docker daemon。考慮 docker 客戶端和 docker daemon 不在同一臺機器上的狀況,build 命令只能從上下文中獲取文件。若是咱們在 Dockerfile 的 COPY 和 ADD 命令中引用了上下文中沒有的文件,就會收到相似下面的錯誤:github
與 WORKDIR 協同工做golang
WORKDIR 命令爲後續的 RUN、CMD、COPY、ADD 等命令配置工做目錄。在設置了 WORKDIR 命令後,接下來的 COPY 和 ADD 命令中的相對路徑就是相對於 WORKDIR 指定的路徑。好比咱們在 Dockerfile 中添加下面的命令:redis
WORKDIR /app
COPY checkredis.py .
而後構建名稱爲 testx 的容器鏡像,並運行一個容器查看文件路徑:sql
checkredis.py 文件就是被複制到了 WORKDIR /app 目錄下。docker
若是僅僅是把本地的文件拷貝到容器鏡像中,COPY 命令是最合適不過的。其命令的格式爲:
COPY <src> <dest>緩存
除了指定完整的文件名外,COPY 命令還支持 Go 風格的通配符,好比:
COPY check* /testdir/ # 拷貝全部 check 開頭的文件
COPY check?.log /testdir/ # ? 是單個字符的佔位符,好比匹配文件 check1.log
對於目錄而言,COPY 和 ADD 命令具備相同的特色:只複製目錄中的內容而不包含目錄自身。好比咱們在 Dockerfile 中添加下面的命令:
WORKDIR /app
COPY nickdir .
其中 nickdir 目錄的結構以下:
從新構建鏡像 testx,運行一個容器並查看 /app 目錄下的內容:
這裏只有 file1 和 file2,少了一層目錄 nickdir。若是想讓 file1 和 file2 還保存在 nickdir 目錄中,須要在目標路徑中指定這個目錄的名稱,好比:
WORKDIR /app
COPY nickdir ./nickdir
COPY 命令區別於 ADD 命令的一個用法是在 multistage 場景下。關於 multistage 的介紹和用法請參考筆者的《Dockerfile 中的 multi-stage》一文。在 multistage 的用法中,可使用 COPY 命令把前一階段構建的產物拷貝到另外一個鏡像中,好比:
FROM golang:1.7.3 WORKDIR /go/src/github.com/sparkdevo/href-counter/ RUN go get -d -v golang.org/x/net/html COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/sparkdevo/href-counter/app . CMD ["./app"]
這段代碼引用自《Dockerfile 中的 multi-stage》一文,其中的 COPY 命令經過指定 --from=0 參數,把前一階段構建的產物拷貝到了當前的鏡像中。
ADD 命令的格式和 COPY 命令相同,也是:
ADD <src> <dest>
除了不能用在 multistage 的場景下,ADD 命令能夠完成 COPY 命令的全部功能,而且還能夠完成兩類超酷的功能:
固然,這些功能也讓 ADD 命令用起來複雜一些,不如 COPY 命令那麼直觀。
解壓壓縮文件並把它們添加到鏡像中
若是咱們有一個壓縮文件包,而且須要把這個壓縮包中的文件添加到鏡像中。需不須要先解開壓縮包而後執行 COPY 命令呢?固然不須要!咱們能夠經過 ADD 命令一次搞定:
WORKDIR /app ADD nickdir.tar.gz .
這應該是 ADD 命令的最佳使用場景了!
從 url 拷貝文件到鏡像中
這是一個更加酷炫的用法!可是在 docker 官方文檔的最佳實踐中卻強烈建議不要這麼用!!docker 官方建議咱們當須要從遠程複製文件時,最好使用 curl 或 wget 命令來代替 ADD 命令。緣由是,當使用 ADD 命令時,會建立更多的鏡像層,固然鏡像的 size 也會更大(下面的兩段代碼來自 docker 官方文檔):
ADD http://example.com/big.tar.xz /usr/src/things/ RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things RUN make -C /usr/src/things all
若是使用下面的命令,不只鏡像的層數減小,並且鏡像中也不包含 big.tar.xz 文件:
RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.xz \ | tar -xJC /usr/src/things \ && make -C /usr/src/things all
好吧,看起來只有在解壓壓縮文件並把它們添加到鏡像中時才須要 ADD 命令!
在使用 COPY 和 ADD 命令時,咱們能夠經過一些技巧來加速鏡像的 build 過程。好比把那些最不容易發生變化的文件的拷貝操做放在較低的鏡像層中,這樣在從新 build 鏡像時就會使用前面 build 產生的緩存。好比筆者構建鏡像時須要用到下面幾個文件:
其中 myhc.py 文件不常常變化,而 checkmongo.py、checkmysql.py 和 checkredis.py 這三個文件則常常變化,那麼咱們可這樣來設計 Dockerfile 文件:
WORKDIR /app
COPY myhc.py .
COPY check* ./
讓 COPY myhc.py . 單獨佔據一個鏡像層,當 build 過一次後,每次因 checkmongo.py、checkmysql.py 和 checkredis.py 這三個文件變化而致使的從新 build 都不會從新 build COPY myhc.py . 鏡像層:
如上圖所示,第二步和第三步都沒有從新 build 鏡像層,而是使用了以前的緩存,從第四步纔開始從新 build 了鏡像層。當文件 size 比較大且文件的數量又比較多,尤爲是須要執行安裝等操做時,這樣的設計對於 build 速度的提高仍是很明顯的。因此咱們應該儘可能選擇可以使用緩存的 Dockerfile 寫法。
當第一次看到 COPY 和 ADD 命令時難免讓人感到疑惑。但分析以後你們會發現 COPY 命令是爲最基本的用法設計的,概念清晰,操做簡單。而 ADD 命令基本上是 COPY 命令的超集(除了 multistage 場景),能夠實現一些方便、酷炫的拷貝操做。ADD 命令在增長了功能的同時也增長了使用它的複雜度,好比從 url 拷貝壓縮文件時弊大於利。但願本文可以解去你們對 Dockerfile 中 COPY 和 ADD 命令的疑惑。
參考:
Docker COPY: Dockerfile best practices
Best practices for writing Dockerfiles
Dockerfile COPY
Dockerfile ADD