Dockerfile 參考手冊(一)

博客原文

最近學習docker過程中,發現Dockerfile是一個非常重要的文檔,本文系統學習一下。
文檔是基于Docker v17.09 版本。
翻譯作品,原文請見官網英文文檔

00 前言

Docker可以讀取Dockerfile中的指令自動構建鏡像,Dockerfile是一個文本文件,它包含很多命令,用戶可以在命令行上調用這些命令組裝鏡像。用戶可以使用docker build來自動構建鏡像,它可以連續執行若干命令行指令。

本文將介紹在Dockerfile中你可以使用命令,你讀完這篇文章之后,Dockerfile
Best Practices
是另一篇很好的指導。

01 用法

docker build命令根據Dockerfile和上下文來構建鏡像,構建過程的上下文是通過PATH或者URL指定的一系列文件。PATH是一個本地文件系統的目錄,URL是一個Git倉庫的位置。

上下文是一個遞歸的處理過程。因此,PATH可以包含任何的子目錄,URL`包括倉庫和它的子模塊。下面是一個構建鏡像的命令的示例,使用當前目錄作為上下文:

$ docker build .
Sending build context to Docker daemon  6.51 MB
...

Build是通過Docker daemon(docker 守護進程),而不是 CLI(命令行界面)執行的。Build過程要做的第一件事是發送整個上下文(遞歸)到Docker的守護進程。最佳實踐是,開始創建一個空的文件夾作為上下文,然后將你的Dockerfile文件放在那個文件夾下,僅添加一些你在編譯Dockerfile過程中需要的文件。

注意:千萬不要使用根路徑 / 作為PATH,這將導致Build會發送你的硬盤上的所有內容到Docker的守護進程。

在Build的上下文中為了使用Dockerfile中指定的一個文件,這個文件是某個指令(例如COPY指令)用到的。為了提高Build的性能,通過添加.dockerignore文件,可以排除上下文目錄中的某些文件和目錄,關于如何創建.dockerignore文件更多信息見本文的下面章節。

一般認為,Dockerfile文件都應該位于上下文的根目錄下,你可以在docker build后使用-f標識來指定你的文件系統中任意位置的Dockerfile文件。

$ docker build -f /path/to/a/Dockerfile .

你還可以指定用來存儲成功編譯的鏡像文件的倉庫和標簽:

$ docker build -t shykes/myapp .

Build的時候也可以為鏡像添加多個倉庫標簽,在你執行Build命令的時候添加多個-t參數即可:

$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

Docker守護進程在執行Dockerfile中的指令之前,會首先對Dockerfile做一個初步校驗,如果有語法錯誤,它會返回一個錯誤:

$ docker build -t test/myapp .
Sending build context to Docker daemon 2.048 kB
Error response from daemon: Unknown instruction: RUNCMD

Docker守護進程是逐步執行Dockerfile中的指令的,如果需要的話,會提交每個指令的結果到新的鏡像中,最后輸出新鏡像的的ID。Docker的守護進程也會自動清除你發送的上下文。

注意,每一條指令都是獨立執行的,因此在創建一個鏡像的時候,RUN cd /tmp這條指令不會對下一條指令有任何影響。

無論任何可能的時候,Docker都將會重用中間狀態(緩存)的鏡像,這樣能夠明顯地加速docker build的過程,這是通過控制臺輸出的信息Using cache來標識的。(更多信息參見,在Dockerfile的最佳實踐指導中的Build cache section):

$ docker build -t svendowideit/ambassador .
Sending build context to Docker daemon 15.36 kB
Step 1/4 : FROM alpine:3.2
 ---> 31f630c65071
Step 2/4 : MAINTAINER SvenDowideit@home.org.au
 ---> Using cache
 ---> 2a1c91448f5f
Step 3/4 : RUN apk update &&      apk add socat &&        rm -r /var/cache/
 ---> Using cache
 ---> 21ed6e7fbb73
Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
 ---> Using cache
 ---> 7ea8aef582cc
Successfully built 7ea8aef582cc

僅在編譯那些有具有本地主鏈的鏡像時使用緩存,意思是這些鏡像的創建依賴前面的Build,或者整個鏡像鏈都已經通過docker load加載進來了。如果你希望對一個指定鏡像使用build cache,你可以使用--cache-from來指定,通過--cache-from指定的鏡像不需要有一個主鏈,也可能是從其他的中心拉取的。

當你編譯完成的時候,你該學習 Pushing a repository to its registry

02 格式

下面是Dockerfile文件的格式:

# Comment
INSTRUCTION arguments

指令對字母大小寫是不敏感的,但是,習慣上將它們大寫,以便容易和參數區分開。

Docker是按照順序來執行Dockerfile中的指令的。一個Dockerfile文件必須以FROM指令開始,FROM指令指定了你正在編譯鏡像的基礎鏡像。在Dockerfile文件中,FROM指令的前面僅可以是一個或者多個ARG指令,這些聲明的參數被用于FROM指令。

Docker認為以#開頭的行是注釋,除非這一行是一個有效的轉義的指令。#標識出現在一行的任何其它地方,都會被認為是一個參數。就像下面這段:

# Comment
RUN echo 'we are running some # of cool things'

注釋中不支持繼續字符。

03 轉義指令

轉義指令是可選的,它會影響在Dockerfile中后續行的處理方式。轉義指令并不會添加任何層到構建的鏡像中,也不會作為構建一個步驟展示,轉義指令是被寫作一個特殊類型的注釋,形式為# directive=value,一個指令可能只會被使用一次。

一旦有一行注釋、空行或者編譯指令被執行,Docker就不會再檢查轉義指令了,而是將任何格式的轉義指令認為是注釋,不會嘗試去驗證它是否是轉義指令。因此所有的轉義指令必須放在Dockerfile文件的第一行。

轉義指令不是大小寫敏感的,但是通常使用小寫的形式,習慣上任何的轉義指令后面都跟一個空行。轉義指令不支持續行符。

根據上面這些規則,下面是一些無效的轉義指令的例子:

由于續行符,導致無效:

# direc \
tive=value

由于出現兩次,導致無效:

# directive=value1
# directive=value2

FROM ImageName

由于出現在了編譯指令之后,被當作了注釋:

FROM ImageName
# directive=value

由于出現在了注釋之后,被當作了注釋,而不是轉義指令:

# About my dockerfile
# directive=value
FROM ImageName

未知的指令由于無法識別被當作了注釋,另外一個已知的指令由于出現在了注釋的后面,被當作了注釋而不是轉義指令。

# unknowndirective=value
# knowndirective=value

轉義指令中允許出現非斷行的空格,所以下面幾行都是相同的:

#directive=value
# directive =value
#   directive= value
# directive = value
#     dIrEcTiVe=value

下面的轉義指令是支持的:
escape

04 轉義符指令

# escape=\ (backslash)

或者

# escape=` (backtick)

escape指令是用來設置Dockerfile中轉義字符的字符,如果不指定的話,默認的轉義字符是\

轉義字符不僅用在一行中的轉義字符上,也用在開啟一個新行。Dockerfile中指令允許是多行的。注意,無論在Dockerfile中是否包含escape轉義指令,在RUN命令中是不會執行轉義的,除非是在一行的末尾。

在Windows環境下,設置轉義字符為 ` ,是非常有用的,由于\是目錄路徑的分隔符,`和windows下的轉義字符是一致的。

考慮下面的一個例子,在windows環境下是失敗的,在第二行的第二個\被解釋成了換行的轉義符,而不是被第一個\轉義了的目標,同樣的,在第三行末尾的\也是,它們被認作是一個指令,\被認為是續行符。這個Dockerfile的結果就是第二行和第三行被認為是一行指令:

FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\

結果是:

PS C:\John> docker build -t cmd .
Sending build context to Docker daemon 3.072 kB
Step 1/2 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/2 : COPY testfile.txt c:\RUN dir c:
GetFileAttributesEx c:RUN: The system cannot find the file specified.
PS C:\John>

一個解決辦法是,上面都使用/作為COPY指令和dir的目標。然而,最好的情況下,這只是看著windows下的路徑不自然,最壞的情況下,并不是所有的windows命令都支持/作為路徑分隔符。

另一種解決辦法,添加一個escape轉義指令,下面的Dockerfile成功的執行,如預期的一樣windows平臺很自然路徑表示語義:

# escape=`

FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\

結果是:

PS C:\John> docker build -t succeeds --no-cache=true .
Sending build context to Docker daemon 3.072 kB
Step 1/3 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/3 : COPY testfile.txt c:\
 ---> 96655de338de
Removing intermediate container 4db9acbb1682
Step 3/3 : RUN dir c:\
 ---> Running in a2c157f842f5
 Volume in drive C has no label.
 Volume Serial Number is 7E6D-E0F7

 Directory of c:\

10/05/2016  05:04 PM             1,894 License.txt
10/05/2016  02:22 PM    <DIR>          Program Files
10/05/2016  02:14 PM    <DIR>          Program Files (x86)
10/28/2016  11:18 AM                62 testfile.txt
10/28/2016  11:20 AM    <DIR>          Users
10/28/2016  11:20 AM    <DIR>          Windows
           2 File(s)          1,956 bytes
           4 Dir(s)  21,259,096,064 bytes free
 ---> 01c7f3bef04f
Removing intermediate container a2c157f842f5
Successfully built 01c7f3bef04f
PS C:\John>

05 環境變量占位符

環境變量(ENV聲明)可以被用在某些指令中作為變量(可以被Dockerfile解釋)。轉義指令也可以用于處理語句中包含類似變量的語法。

環境變量在Dockerfile中表示為$variable_name 或者 ${variable_name},他們是等效的,大括號的語法通常用來強調沒有空格的變量名,例如${foo}_bar${variable_name}語法也支持一些標準的bash修飾符,例如下面:

  • ${variable:-word}意思是,如果variable被設置了,結果將是那個值,如果variable沒被設置,那個word就是結果。
  • ${variable:+word}意思是,如果variable被設置了,word就是結果,否則結果就是空。

以上所有情形,word可以是任何字符串,包括其它的環境變量。

轉義可以在變量之前添加\:例如,\$foo或者\${foo}將被轉義為$foo${foo}兩個常量。

舉個例子(轉義之后的結果展示在#的后面):

FROM busybox
ENV foo /bar
WORKDIR ${foo}   # WORKDIR /bar
ADD . $foo       # ADD . /bar
COPY \$foo /quux # COPY $foo /quux

環境變量在下面這些Dockerfile指令中都是支持的:

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR

此外還有:

  • ONBUILD(當與上面任何一個指令結合時)

注意:在1.4版本之前,ONBUILD是不支持環境變量的,即使與上面列出的指令結合時。

在整個指令中環境變量的替換值都是用同一個值,換句話說,就是下面的例子:

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

結果是,def的值是hello,而不是byeghi的值是bye,因為它不是設置abcbye的指令的一部分。

06 .dockerignore文件

在docker命令行界面中發送上下文到docker的守護進程之前,它會檢查上下文目錄根路徑下名為.dockerignore的文件,如果這個文件存在,命令行界面會修改上下文,排除那些被.dockerignore中的模式匹配到的文件和目錄。這有助于避免一些不必要的(大的或者敏感的文件和目錄)發送到守護進程,還能避免一些潛在的使用ADD 或者 COPY添加文件和目錄到鏡像中。

命令行解釋.dockerignore文件為一個換行符分割的模式列表,類似于Unix shell的glob文件。由于這個匹配的目的,上下文的根被認為是工作目錄和根目錄。例如,模式 /foo/barfoo/bar都是在排除目錄foo下面一個叫bar的文件或者目錄,目錄fooPATH的子目錄或者URL指定的git倉庫下的子目錄。不排除任何其它的。

如果在.dockerignore文件中有一行以#開頭,那么這一行被認為是注釋,命令行解釋之前為忽略它。

下面是一個.dockerignore文件的例子:

# comment
*/temp*
*/*/temp*
temp?

這個文件將引發下面的構建行為:

規則 行為
# comment 忽略。
*/temp* 排除根目錄下的子目錄中任何以temp開頭的文件和目錄,例如,/somedir/temporary.txt 這個文本文件會被排除,/somedir/temp這個目錄也會被排除。
*/*/temp* 排除來自子目錄的任何以temp開頭的文件和目錄,這個子目錄是根目錄下兩層,例如,/somedir/subdir/temporary.txt被排除的。
temp? 排除那些根目錄下名字以temp開始拓展一個字符的文件和目錄,例如,/tempa/tempb是被排除的。

完成這個匹配使用的是Go語言的文件路徑匹配規則,在預處理步驟中會去除掉開頭和結尾的空格,并清除...元素,在這個過程中使用的是Go語言的文件路徑清理方法,預處理過程中會忽略掉空白行。

在Go語言的文件路徑匹配規則之外,Docker還支持一個特殊的通配符**,用于匹配任意數量的目錄(包括零),例如,**/*.go將排除所有以.go結尾的文件,它會在編譯上下文的根目錄的所有目錄中找。

以感嘆號!開始的行被用于標出排除中的異常文件,下面的這個.dockerignore文件的例子就使用了這種機制:

*.md
!README.md

在上下文中除了README.md之外,所有markdown文件都會被排除。

異常規則!的位置影響行為:.dockerignore文件的最后一行匹配一個特定文件,它是包含還是排除呢?看下面的例子:

*.md
!README*.md
README-secret.md

除了README 文件之外,沒有任何markdown文件被包含進上下文,并沒有README-secret.md

現在看這個例子:

*.md
README-secret.md
!README*.md

所有的README文件都會被包含進去,中間一行是沒有任何影響的,因為!README*.md 能夠匹配 README-secret.md,并且在后面。

你甚至可以用.dockerignore來排除Dockerfile文件和.dockerignore,這些文件仍然是會被送到守護進程的,因為需要它們做這些工作,但是ADDCOPY指令是不會copy它們到鏡像中去的。

最后,你可能想要指定文件包含進上下文,而不是排除它們,為了實現這個目的,可以使用*作為第一個模式,下面使用一個或者多個!異常模式。

注意:由于歷史原因,模式.是被忽略的。

到此為止介紹Dockerfile文件中工作原理和一些語法,以及相關的一些東西,其中03和04節不太常用,翻譯不是太好,請高手指正。Dockerfile中常用的指令下一篇文章再介紹。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 博客原文 本文接著上一篇文章《Dockerfile 參考手冊(一)》接續Dockerfile相關的學習。本文主要介...
    rabbitGYK閱讀 1,636評論 0 9
  • docker基本概念 1. Image Definition 鏡像 Image 就是一堆只讀層 read-only...
    慢清塵閱讀 8,795評論 1 21
  • 昨天在實驗室正在趕作業,朋友突然跑過來坐在我旁邊,摘下眼鏡,眼淚就嘩啦啦地流下來了,我趕緊遞上紙,一臉懵逼。幾分鐘...
    曾好看吶閱讀 884評論 3 5
  • 一群孩童從她身邊飛奔而過,帶來了些許微風。她知道,孩子們是奔向街口的小賣部,那里有各種口味的冰淇淋和雪糕。 今天是...
    龍朱朱閱讀 3,169評論 36 57
  • 才二月底,樓下的玉蘭已長出了花骨朵。躍躍欲試,一副含苞待放的樣子。街道兩邊的柳樹遠遠望去,枝條隨風搖擺,朦朧中若有...
    云水居閱讀 696評論 2 3