Dockerfile是一個文本文件,包含一些Docker指令。執行docker build,Docker就會執行Dockerfile里面的指令,來自動創建鏡像。
用法

Dockerfile里面的指令可以訪問context這些文件。
context是遞歸的,PATH包含所有子目錄,URL包含所有子模塊。
例子,把當前目錄當做context,
$ docker build .
Sending build context to Docker daemon 6.51 MB
...
build是由Docker daemon(守護進程)來運行,而不是CLI。
build會把整個context發給daemon。所以最好把context設置為空目錄,把Dockerfile放進去。只添加需要的文件,為了提高build性能,還可以添加.dockerignore來排除一些文件和目錄。
Warning!不要用系統根目錄/作為PATH,不然會把根目錄下所有東西都傳給Docker daemon。
一般會把Dockerfile放在context根目錄下,也可以使用-f來指定其他路徑,
$ docker build -f /path/to/a/Dockerfile .
指定鏡像存放倉庫可以使用-t,
$ docker build -t shykes/myapp .
支持多個,
$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
Docker daemon在執行Dockfile的指令前,會做檢查,如果有語法錯誤會報錯,
$ docker build -t test/myapp .
Sending build context to Docker daemon 2.048 kB
Error response from daemon: Unknown instruction: RUNCMD
Docker daemon執行指令,是一個一個執行,一個一個提交的。執行結束會生成鏡像ID。自動清理context。
RUN cd /tmp是無效的,因為daemon是獨立執行每條指令的,不會作用到后面的指令。
為了加速build過程,Docker會重復使用中間鏡像(緩存),在console日志中可以看到Using cache,
$ 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
cache來源於之前本地build過的鏡像,或者使用docker load加載的鏡像。
如果想直接指定一個鏡像作為cache,可以使用--cache-from。
格式
# Comment
INSTRUCTION arguments
#開頭是注釋或者parser directive(提示解析器做特殊處理)。
指令是忽略大小寫的,不過為了和參數區分,一般全大寫。
Dockerfile從上往下順序執行指令,第一條指令必須是FROM,定義build的parent image(父鏡像)。沒有parent的鏡像叫base image。

參數里面的#就不是注釋了,是參數的一部分,
# Comment
RUN echo 'we are running some # of cool things'
注釋在Dockerfile指令執行前,會被移除。以下是等價的,
RUN echo hello \
# comment
world
RUN echo hello \
world
注意,注釋不支持換行符\。
注釋和指令前面的空格會被忽略,以下是等價的,
# this is a comment-line
RUN echo hello
RUN echo world
# this is a comment-line
RUN echo hello
RUN echo world
但是參數里面的空格,是會被保留的,
RUN echo "\
hello\
world"
Parser directives
# directive=value
Parser directives是一種特殊的注釋,用來提示解析器做特殊處理。
但是Parser directives並不會添加layers到build中,也不會被識別為build step。
如果注釋、空行、或者指令被運行后,Docker就不會再識別Parser directives了,所以必須把Parser directives放在Dockerfile的最前面的最前面。
Parser directives是忽略大小寫的,不過一般約定為全小寫。同時約定隨后跟一個空行。
Parser directives不支持換行符。
以下是一些無效示例,
無效--換行符
# direc \
tive=value
無效--出現了2次
# directive=value1
# directive=value2
FROM ImageName
無效--在指令之后就是普通的注釋
FROM ImageName
# directive=value
無效--在普通注釋之后也變成了普通注釋
# About my dockerfile
# directive=value
FROM ImageName
無效--未知命令會被視為普通注釋,普通注釋之后也是普通注釋
# unknowndirective=value
# knowndirective=value
Parser directives同一行的空格會被忽略,以下是等價的,
#directive=value
# directive =value
# directive= value
# directive = value
# dIrEcTiVe=value
目前支持2個Parser directives,
syntax,依賴BuildKitescape
escape
反斜杠(默認)
# escape=\
或者反引號
# escape=`
用來指定轉義符。這個在Windows系統很有用,因為\在Windows是路徑分隔符。
比如,
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>
使用escape可以替換\為`
# 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>
環境替換
環境變量(使用ENV指令來定義環境變量)能夠用在指令中作為變量,被Dockerfile解釋。還可以處理轉義符,以便在語句中照字面值地包含variable-like語法。
使用$variable_name或${variable_name}來引用環境變量。
可以使用雙括弧和下划線來命名,如${foo}_bar。同時支持bash修飾符,
${variable:-word}setvariable后就是set的值,沒有setvariable值就是word${variable:+word}setvariable后值就是word,沒有setvariable就是空字符串
word既可以是string,也可以是另外一個環境變量。
可以在變量前加轉義符,比如\$foo ,\${foo}會被分別轉義為$foo 和${foo}。
示例,
FROM busybox
ENV foo /bar
WORKDIR ${foo} # WORKDIR /bar
ADD . $foo # ADD . /bar
COPY \$foo /quux # COPY $foo /quux
Dockerfile的一下指令都支持環境變量
ADDCOPYENVEXPOSEFROMLABELSTOPSIGNALUSERVOLUMEWORKDIRONBUILD(結合以上指令使用)
需要注意的是,變量替換是針對整條指令的,
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc
def的值是hello,而不是bye,因為上一條指令賦值的hello。
ghi的值才會是bye。
.dockerignore file
.dockerignore文件位於context根目錄,會把匹配到的文件和目錄排除在context之外。
這樣就可以在使用ADD和COPY命令時,避免把一些大文件或者敏感信息文件和目錄,發送到Docker daemon。
context是由PATH和URL定義的,所以.dockerignore文件會匹配這2個路徑。
/foo/bar == foo/bar
示例,
# comment
*/temp*
*/*/temp*
temp?
| Rule | Behavior |
|---|---|
# comment |
注釋忽略 |
*/temp* |
排除root的子目錄下,temp開頭的文件和目錄。 如 /somedir/temporary.txt 和 /somedir/temp |
*/*/temp* |
排除root的二層目錄下,temp開頭的文件和目錄。如 /somedir/subdir/temporary.txt |
temp? |
排除root下, temp+1個字符的文件和目錄。如 /tempa 和/tempb |
匹配遵循Go語言的filepath.Match規則。
Docker還支持**,匹配任意數量的目錄(包括0)。如**/*.go排除.go結尾的,包括context root下所有目錄。
如果排除了一堆文件后,想只包含其中幾個文件,可以使用異常規則!。
示例,排除.md結尾的文件,包含README.md,
*.md
!README.md
README-secret.md不會被排除,因為!README*.md能匹配到README-secret.md,又把README-secret.md包含進來了。
.dockerignore文件甚至可以排除Dockerfile 和.dockerignore,然而並沒有什么卵用,這些文件還是會被發送到Docker daemon,只是ADD和COPY命令不會把它們復制到鏡像了。
FROM
FROM指令初始化一個新的buid stage,為后面的指令設置Parent Image。
FROM [--platform=<platform>] <image> [AS <name>]
或
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
或
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
--platform,用來定義image的平台,如linux/amd64, linux/arm64, 或者windows/amd64,這樣就能支持多平台鏡像。
tag digest是可選的,都不填時,默認用最新的tag。如果找不到tag,builder就會報錯。
AS name可以給image取個別名,在后續FROM和COPY --from=<name|index>指令中可以使用這個別名。
可以在一個Dockerfile文件中使用多個FROM。每個FROM都會把上個指令創建的狀態清除。所以在每個新的FROM指令之前,記錄commit輸出的最后一個image ID。
ARG是唯一能在FROM之前的指令。
比如--platform,默認情況下,會使用build請求的默認平台。也可以使用全局build參數,通過automatic platform ARGs(依賴BuildKit)來強制把stage指定為本地build平台(--platform=$BUILDPLATFORM),然后用它來在stage中cross-compile目標平台。
FROM和ARG怎么結合使用呢?
FROM指令支持出現在第一個FROM之前的ARG聲明的變量。
ARG CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app
FROM extras:${CODE_VERSION}
CMD /code/run-extras
FROM之前聲明的ARG是在build stage之外的,所以它不能用在FROM后的任何指令中。如果要用,可以使用在build stage中的不帶value的ARG指令,
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
RUN
RUN <command>(shell 格式,Linux/bin/sh -cWindowscmd /S /C)RUN ["executable", "param1", "param2"](exec 格式)
RUN指令會在當前鏡像之上的新layer中執行命令,commit結果,commit后的鏡像會在Dockerfile的下一個step中使用。
RUN指令的commits符合Docker理念,commit is cheap,containers可以從image歷史中任何記錄創建,就像source control。
可以使用不同的SHELL,
shell格式
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
exec格式
RUN ["/bin/bash", "-c", "echo hello"]
shell格式會調用command shell,而exec格式不會,所以exec中$HOME是沒用的,要用的話直接執行shell RUN [ "sh", "-c", "echo $HOME" ]。
注意,exec格式被解析為JSON數組,所以只能用雙引號。還需注意反斜杠,
錯誤
RUN ["c:\windows\system32\tasklist.exe"]
正確
RUN ["c:\\windows\\system32\\tasklist.exe"]
默認是會啟動RUN的緩存的,比如RUN apt-get dist-upgrade -y會在下次build的時候復用。可以使用docker build --no-cache來禁用緩存。
使用ADD和COPY指令也可以禁用RUN緩存。
CMD
CMD和RUN是不同的。RUN指令是在build過程中執行command和commit結果。CMD在build時不會執行任何command,而是為image定義command,在container(鏡像創建的容器)啟動的時候執行。
CMD ["executable","param1","param2"](exec 格式,首選)CMD ["param1","param2"](ENTRYPOINT默認參數)CMD command param1 param2(shell 格式)
一個Dockerfile只能有一個CMD指令,如果有多個,只有最后一個生效。
shell格式會調用command shell,而exec格式不會,所以exec中$HOME是沒用的,要用的話直接執行shell RUN [ "sh", "-c", "echo $HOME" ]。
注意,exec格式被解析為JSON數組,所以只能用雙引號。還需注意反斜杠。
如果想要container每次運行相同的可執行文件,需要結合 ENTRYPOINT使用。
如果docker run定義了參數,那么會覆蓋CMD定義。
LABEL
LABEL <key>=<value> <key>=<value> <key>=<value> ...
LABEL用來給image添加metadata,是key-value鍵值對的形式。
示例,
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
一個image可以有多個label,一個label可以有多個鍵值對,以下是等價的,
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
label會隨着image繼承,從base image或parent image繼承到當前image。
重復的label,會用最新的覆蓋舊的。
可以使用命令查看image的labels,
docker image inspect --format='' myimage
{
"com.example.vendor": "ACME Incorporated",
"com.example.label-with-value": "foo",
"version": "1.0",
"description": "This text illustrates that label-values can span multiple lines.",
"multi.label1": "value1",
"multi.label2": "value2",
"other": "value3"
}
MAINTAINER
MAINTAINER 已經棄用了,直接使用LABLE,
LABEL maintainer="SvenDowideit@home.org.au"
EXPOSE
EXPOSE <port> [<port>/<protocol>...]
EXPOSE定義了container監聽的網絡端口,支持TCP和UDP,默認TCP。
EXPOSE並不真正的發布端口,而只是一種預定義。
真正發布是在docker run的時候,使用-p或-P來發布。
-p發布一個或多個端口,-P發布全部,並映射到高位端口。
示例,默認TCP,可以定義UDP,
EXPOSE 80/udp
也可以同時定義TCP和UDP,
EXPOSE 80/tcp
EXPOSE 80/udp
如果這里docker run使用了-P,將會暴露一次TCP端口和一次UDP端口,由於會映射到高位端口,它們的端口會不一樣。
使用-p指定端口,
docker run -p 80:80/tcp -p 80:80/udp ...
也可以使用docker network來創建網絡在container之間通信而不需要暴露任何端口。因為container可以使用任何端口通信。
ENV
ENV <key> <value>
ENV <key>=<value> ...
ENV用來設置環境變量。有2種形式,以下是等價的,
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy
可以使用docker inspect來查看環境變量。也可以使用docker run --env <key>=<value>來修改環境變量。
ENV的作用域除了build,還包括container running。有時候會有副作用,比如ENV DEBIAN_FRONTEND noninteractive,所有操作都是非交互式的,無需向用戶請求輸入,直接運行命令。可能會使apt-get用戶誤認為是一個Debian-based image。正確的做法是為command添加單獨的環境變量,如RUN apt-get install -y python3。
ADD
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
ADD有2種形式,第2種是為了支持路徑包含空格,所以加了雙引號。
--chown只適用於Linux container,對Windows無效。
ADD的作用是從<src>復制新文件,目錄或者遠程文件URLs,然后添加到<desc>所在的image文件系統。
src如果是文件和目錄,那么就是相對路徑,相對於build的context。同時支持通配符,遵循Golang的filepath.Match規則。
示例,添加所有以"hom"開頭的文件,
ADD hom* /mydir/
用?匹配單個字符,
ADD hom?.txt /mydir/
<dest>是絕對路徑,或者WORKDIR的相對路徑。
示例,絕對路徑,
ADD test.txt /absoluteDir/
相對路徑,<WORKDIR>/relativeDir/,
ADD test.txt relativeDir/
如果路徑種包含特殊字符(如[和]),那么需要進行轉義,
示例,添加一個文件arr[0].txt,
ADD arr[[]0].txt /mydir/
針對Linux,可以使用--chown定義username、groupname或者UID/GID,默認新文件和目錄會被設置為UID為0,GID為0。
如果只設置username不設置groupname,或只設置UID不設置GID,GID會使用和UID相同的數值。
username和groupname會被container's root filesystem /etc/passwd and /etc/group 轉換為UID/GID。如果container沒有這2個文件,在設置了username/groupname后,就會報錯。可以通過設置UID/GID來避免。
示例,
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/
如果build使用STDIN (docker build - < somefile),就沒有build context,就只能用ADDURL。也可以在使用STDIN時添加壓縮包 (docker build - < archive.tar.gz),壓縮包根目錄的Dockerfile和其他壓縮包會當做build context。
如果src是一個遠程文件URL,就會需要600權限(Linux)。如果遠程文件有HTTP Last-Modified header,header的timestamp會用來設置到dest文件的mtime。但是mtime不會反映文件是否修改和緩存是否應該更新。
如果URL文件需要授權,ADD是不支持的,需要使用RUN wget, RUN curl,或者container里面的其他工具。
ADD遵循以下規則:
<src>必須在build的context 中;不能ADD ../something /something添加context父目錄的東西。因為docker build的第一步是把context,目錄及其子目錄發送到docker daemon。- 如果
<src>是URL,<dest>沒有以斜杠結尾,那么文件從直接從URL下載后,然后直接復制到<dest>。 - 如果
<src>是URL,<dest>是以斜杠結尾的,那么會從URL解析出文件名,下載到<dest>/<filename>。比如,ADD http://example.com/foobar dest/會創建文件dest/foobar。URL必須是明確的路徑,以保證能找到合適的文件名(http://example.com是無效的)。 - 如果
<src>是目錄,那么整個目錄都會被復制,包括文件系統的metadata。(目錄本身不復制,只是內容) - 如果
<src>是本地壓縮包(如gzip, bzip2 or xz),那么會被解壓成目錄。遠程URL是不會解壓的。解壓相當於執行了tar -x,如果dest路徑下有文件沖突,會被重命名為“2”。(壓縮包不是根據文件名判斷的,而是根據內容,比如一個空文件命名為.tar.gz,是不會被解壓復制的) - 如果
<src>是任何其他文件,就會隨同它的metadata一起復制。此時<dest>以斜杠/結尾的話,就會被認為是一個目錄,<src>的內容會被寫到<dest>/base(<src>)。 - 如果
<src>定義的是多個資源,不論是直接還是通配符匹配到的,<dest>必須是一個目錄,且以斜杠/結尾。 - 如果
<dest>不以斜杠結尾,那么就會被認為是一個普通文件,那么<src>會被寫到<dest>。 - 如果
<dest>不存在,那么path中的所有未創建的目錄都會自動創建。
如果src內容改變了,在第一次遇到ADD指令后,會禁用后續所有指令的緩存,包括RUN指令的緩存。
COPY
COPY和ADD的區別在於ADD可以添加遠程URLS,COPY不能。
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
COPY有2種形式,第2種是為了支持路徑包含空格,所以加了雙引號。
--chown只適用於Linux container,對Windows無效。
COPY的作用是從<src>復制新文件,目錄,然后添加到<desc>所在的image文件系統。
src如果是文件和目錄,那么就是相對路徑,相對於build的context。同時支持通配符,遵循Golang的filepath.Match規則。
示例,添加所有以"hom"開頭的文件,
COPY hom* /mydir/
用?匹配單個字符,
COPY hom?.txt /mydir/
<dest>是絕對路徑,或者WORKDIR的相對路徑。
示例,絕對路徑,
COPY test.txt /absoluteDir/
相對路徑,<WORKDIR>/relativeDir/,
COPY test.txt relativeDir/
如果路徑種包含特殊字符(如[和]),那么需要進行轉義,
示例,添加一個文件arr[0].txt,
COPY arr[[]0].txt /mydir/
針對Linux,可以使用--chown定義username、groupname或者UID/GID,默認新文件和目錄會被設置為UID為0,GID為0。
如果只設置username不設置groupname,或只設置UID不設置GID,GID會使用和UID相同的數值。
username和groupname會被container's root filesystem /etc/passwd and /etc/group 轉換為UID/GID。如果container沒有這2個文件,在設置了username/groupname后,就會報錯。可以通過設置UID/GID來避免。
示例,
COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/
如果build使用STDIN (docker build - < somefile),就沒有build context,就不能用COPY。
COPY支持--from=<name|index>,用來指定src為之前buid的image(通過FROM .. AS <name>創建的)來替換build context。既可以是name也可以是index數字(所有使用FROM指令建立的build stages)。如果通過name找不到build stage,就會去找同名的image。
COPY遵循以下規則:
<src>必須在build的context 中;不能COPY ../something /something添加context父目錄的東西。因為docker build的第一步是把context,目錄及其子目錄發送到docker daemon。- 如果
<src>是目錄,那么整個目錄都會被復制,包括文件系統的metadata。(目錄本身不復制,只是內容) - 如果
<src>是任何其他文件,就會隨同它的metadata一起復制。此時<dest>以斜杠/結尾的話,就會被認為是一個目錄,<src>的內容會被寫到<dest>/base(<src>)。 - 如果
<src>定義的是多個資源,不論是直接還是通配符匹配到的,<dest>必須是一個目錄,且以斜杠/結尾。 - 如果
<dest>不以斜杠結尾,那么就會被認為是一個普通文件,那么<src>會被寫到<dest>。 - 如果
<dest>不存在,那么path中的所有未創建的目錄都會自動創建。
如果src內容改變了,在第一次遇到COPY指令后,會禁用后續所有指令的緩存,包括RUN指令的緩存。
ENTRYPOINT
exec 格式
ENTRYPOINT ["executable", "param1", "param2"]
shell 格式
ENTRYPOINT command param1 param2
ENTRYPOINT用來配置container作為可執行文件來運行。
示例,使用默認內容啟動nginx,監聽80端口,
$ docker run -i -t --rm -p 80:80 nginx
docker run <image>的命令行參數,會被添加到exec格式中的所有元素之后,並覆蓋CMD指令定義的元素。這樣就可以把參數傳遞給entry point,也就是docker run <image> -d會把-d傳遞給entry point。可以使用docker run --entrypoint來覆蓋ENTRYPOINT指令(但是只能把binary設置為exec,不能用sh -c)。
shell格式會禁用掉CMD或者run命令行參數,但是有個缺點就是,ENTRYPOINT就不是作為/bin/sh -c的子命令來啟動的了,也就是不能傳遞signals。也就意味着可執行文件,不是container的PID 1,也不會接收Unix signals(一種軟件中斷)。這樣可執行文件就不會接收來自docker stop <container>的SIGTERM。
只有Dockerfile的最后一個ENTRYPOINT才會生效。
ENTRYPOINT Exec示例
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
當運行container,top是唯一進程,
$ docker run -it --rm --name test top -H
top - 08:25:00 up 7:27, 0 users, load average: 0.00, 0.01, 0.05
Threads: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 2056668 total, 1616832 used, 439836 free, 99352 buffers
KiB Swap: 1441840 total, 0 used, 1441840 free. 1324440 cached Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 19744 2336 2080 R 0.0 0.1 0:00.04 top
為了驗證更多結果,使用docker exec,
$ docker exec -it test ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 2.6 0.1 19752 2352 ? Ss+ 08:24 0:00 top -b -H
root 7 0.0 0.1 15572 2164 ? R+ 08:25 0:00 ps aux
top -b -H,其中top -b是ENTRYPOINT設置的,-H是docker命令行參數,添加到了ENTRYPOINT后面,覆蓋了CMD的-c。
然后可以優雅地使用docker stop test請求top shut down。
示例,使用ENTRYPOINT在前台運行Apache(也就是PID 1),
FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
如果想編寫單個可執行文件的啟動腳本,可以使用exec和gosu命令,來確保可執行文件能夠接收到Unix signals。
#!/usr/bin/env bash
set -e
if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA"
if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi
exec gosu postgres "$@"
fi
exec "$@"
最后,如果在shutdown的時候需要做一些額外的清理(或者和其他containers交互),或者是多個協調而不是單個可執行文件,就可能需要確保ENTRYPOINT腳本能夠接收Unix signals,傳遞,然后做更多工作,
#!/bin/sh
# Note: I've written this using sh so it works in the busybox container too
# USE the trap if you need to also do manual cleanup after the service is stopped,
# or need to start multiple services in the one container
trap "echo TRAPed signal" HUP INT QUIT TERM
# start service in background here
/usr/sbin/apachectl start
echo "[hit enter key to exit] or run 'docker stop <container>'"
read
# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stop
echo "exited $0"
如果使用docker run -it --rm -p 80:80 --name test apache來運行這個image,那么就可以使用docker exec或docker top來驗證container處理,然后使用腳本停止Apache,
$ docker exec -it test ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.1 0.0 4448 692 ? Ss+ 00:42 0:00 /bin/sh /run.sh 123 cmd cmd2
root 19 0.0 0.2 71304 4440 ? Ss 00:42 0:00 /usr/sbin/apache2 -k start
www-data 20 0.2 0.2 360468 6004 ? Sl 00:42 0:00 /usr/sbin/apache2 -k start
www-data 21 0.2 0.2 360468 6000 ? Sl 00:42 0:00 /usr/sbin/apache2 -k start
root 81 0.0 0.1 15572 2140 ? R+ 00:44 0:00 ps aux
$ docker top test
PID USER COMMAND
10035 root {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054 root /usr/sbin/apache2 -k start
10055 33 /usr/sbin/apache2 -k start
10056 33 /usr/sbin/apache2 -k start
$ /usr/bin/time docker stop test
test
real 0m 0.27s
user 0m 0.03s
sys 0m 0.03s
shell格式會調用command shell,而exec格式不會,所以exec中$HOME是沒用的,要用的話直接執行shell RUN [ "sh", "-c", "echo $HOME" ]。
注意,exec格式被解析為JSON數組,所以只能用雙引號。還需注意反斜杠。
ENTRYPOINT Shell示例
ENTRYPOINT定義一個簡單的string,然后它就會在/bin/sh -c中執行。shell格式使用shell processing來替代shell environment variables,然后會忽略任何CMD或docker run命令行參數。為了確保docker stop能直接signal任何運行的ENTRYPOINT可執行文件,記住使用exec開始,
FROM ubuntu
ENTRYPOINT exec top -b
運行這個image時,你會看到單個PID 1進程,
$ docker run -it --rm --name test top
Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU: 5% usr 0% sys 0% nic 94% idle 0% io 0% irq 0% sirq
Load average: 0.08 0.03 0.05 2/98 6
PID PPID USER STAT VSZ %VSZ %CPU COMMAND
1 0 root R 3164 0% 0% top -b
執行docker stop,也會干凈的退出,
$ /usr/bin/time docker stop test
test
real 0m 0.20s
user 0m 0.02s
sys 0m 0.04s
如果忘了在ENTRYPOINT前添加exec,
FROM ubuntu
ENTRYPOINT top -b
CMD --ignored-param1
運行(為下一步設置一個name),
$ docker run -it --name test top --ignored-param2
Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cached
CPU: 9% usr 2% sys 0% nic 88% idle 0% io 0% irq 0% sirq
Load average: 0.01 0.02 0.05 2/101 7
PID PPID USER STAT VSZ %VSZ %CPU COMMAND
1 0 root S 3168 0% 0% /bin/sh -c top -b cmd cmd2
7 1 root R 3164 0% 0% top -b
你就會看到ENTRYPOINT定義的top不是PID 1。
如果執行docker stop test,container就不會干凈地退出。stop命令會在超時后被強制發送一個SIGKILL,
$ docker exec -it test ps aux
PID USER COMMAND
1 root /bin/sh -c top -b cmd cmd2
7 root top -b
8 root ps aux
$ /usr/bin/time docker stop test
test
real 0m 10.19s
user 0m 0.04s
sys 0m 0.03s
real 10.19s超時。
CMD和ENTRYPOINT如何結合使用
CMD和ENTRYPOINT指令都定義了運行container時,哪些命令會執行。他們的結合有一些規則,
- Dockerfile應該定義至少一個
CMD或ENTRYPOINT。 - 如果使用container作為可執行文件,應該定義
ENTRYPOINT。 - 如果需要給
ENTRYPOINT定義默認參數,或者在container中執行ad-hoc(臨時)命令,應該使用CMD。 - 以可選參數運行container時會覆蓋
CMD。
下面這個表格展示了CMD和ENTRYPOINT指令的不同組合
| No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT [“exec_entry”, “p1_entry”] | |
|---|---|---|---|
| No CMD | error, not allowed | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry |
| CMD [“exec_cmd”, “p1_cmd”] | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd |
| CMD [“p1_cmd”, “p2_cmd”] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
| CMD exec_cmd p1_cmd | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
注意,如果CMD是從base image定義的,那么設置ENTRYPOINT會重置CMD為空值。此時如果要使用CMD,必須在當前image重新定義。
VOLUME
VOLUME ["/data"]
VOLUME指令用來創建掛載點,把container掛載到native host(宿主機)或其他container。

value可以是JSON array,如VOLUME ["/var/log/"],也可以是string,如VOLUME /var/log或VOLUME /var/log /var/db。
docker run命令會用base image中定義的location中存在的任何數據,來初始化新創建的volumn。
示例,
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
docker run會在/myvol創建一個掛載點,然后把greeting復制到新創建的volumn。
遵循規則,
- **基於Windows的containers **: volumn的目標路徑必須是以下之一:
- 不存在的或者空的目錄
- 除
C:以外的驅動
- 在Dockerfile里面修改volumn: 在volumn已經被聲明之后的任何build steps嘗試修改volumn數據,都會被忽略。
- JSON formatting: 要用雙引號,不要用單引號.
- 在container run-time才會聲明主機目錄(掛載點): 掛載點是依賴主機的。因為主機目錄不能保證對所有主機都是有用的,為了保證image的可移植性,不能在Dockerfile中掛載主機目錄,而是必須在創建或運行container的時候。
VOLUME指令也不支持host-dir這樣的參數。
USER
USER <user>[:<group>]
或
USER <UID>[:<GID>]
USER指令用於RUN, CMD 和ENTRYPOINT指令執行時指定user name / group。USER指令可以設置user name(或UID),可選用user group(或GID)。
如果定義了user group,那么這個user就只有這個group的membership,任何其他配置的group memberships都會被忽略。
如果user沒有primary group,那么image(或者下一條指令)就會以root group運行。
在Windows,如果不是內建賬號,必須先創建。可以在Dockerfile中調用net user命令,
FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick
WORKDIR
WORKDIR /path/to/workdir
WORKDIR為RUN, CMD, ENTRYPOINT, COPY and ADD指令設置工作目錄。
如果WORKDIR不存在,即使后面的Dockerfile不會用到,它仍然會被創建。
WORKDIR指令可以在Dockerfile中定義多次。如果是相對路徑,那么就是相對於上一條WORKDIR指令的路徑。
示例,
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
pwd的結果是/a/b/c。
WORKDIR可以引用ENV定義的環境變量,示例,
ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
pwd的結果是/path/$DIRNAME。
ARG
ARG <name>[=<default value>]
ARG指令定義變量,用戶可以在使用docker build命令帶參數--build-arg <varname>=<value>,在build-time傳遞這個變量給builder。如果用戶指定了一個build參數而沒有在Dockerfile中定義,build會報warning,
[Warning] One or more build-args [foo] were not consumed.
一個Dockerfile可以包含一個或多個ARG指令。
示例,
FROM busybox
ARG user1
ARG buildno
# ...
警告!不建議使用build-time變量來傳遞私密數據,如github keys,用戶認證信息等。因為image的任何用戶都可以使用docker history查看build-time變量。
默認值
ARG指令可以設置默認值(可選),
FROM busybox
ARG user1=someuser
ARG buildno=1
# ...
如果ARG指令有默認值,在build-time沒有值傳遞,那么builder會用這個默認值。
范圍
ARG指令是在它被定義那一行生效的,而不是命令行被使用的時候,或者其他地方。
示例,
FROM busybox
USER ${user:-some_user}
ARG user
USER $user
# ...
用戶build這個文件,調用,
$ docker build --build-arg user=what_user .
第2行的USER結果為some_user因為user變量是在第3行定義的。
第4行的USER結果為what_user,因為user變量已經被定義了,在命令行傳遞了what_user值。
在ARG指令定義之前,任何變量使用結果都是空string。
在ARG定義的build stage結束時,ARG指令就超出范圍了。為了在多個stages使用同一個arg,每個stage都必須包括ARG指令,
FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS
FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS
使用ARG變量
可以使用ARG或ENV指令來為RUN指令定義變量。ENV定義的環境變量始終都會覆蓋ARG定義的同名變量。
示例,
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER v1.0.0
RUN echo $CONT_IMG_VER
假設使用這條命令build image,
$ docker build --build-arg CONT_IMG_VER=v2.0.1 .
RUN會使用v1.0.0而不是ARG傳遞的v2.0.1。這個行為有點類似於shell腳本,一個局部變量會覆蓋通過參數傳遞的變量,或者從環境定義繼承的變量。
還是上面的例子,定義不同的ENV會把ARG和ENV結合的更好用,
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER
不像ARG,ENV的值會在build image中持久化。如果不用--build-arg build,
$ docker build .
用這個Dockerfile,CONT_IMG_VER仍然會持久化在這個image,它的值是v1.0.0,因為在第3行用ENV定義了默認值。
在這個示例中,通過ENV指令,可以把命令行參數傳遞進來,然后持久化到最終的image,實現了變量擴展。變量擴展只支持Dockerfile指令的一部分指令。
ADDCOPYENVEXPOSEFROMLABELSTOPSIGNALUSERVOLUMEWORKDIRONBUILD(結合以上指令使用)
預定義ARGs
Docker有一些預定義的ARG變量,你可以不使用ARG指令,直接用這些變量。
HTTP_PROXYhttp_proxyHTTPS_PROXYhttps_proxyFTP_PROXYftp_proxyNO_PROXYno_proxy
直接在命令行使用,
--build-arg <varname>=<value>
默認這些預定義的變量是不會輸出到docker history中的。這樣可以降低在HTTP_PROXY變量中意外泄露敏感認證信息的風險。
示例,使用--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com來build Dockerfile,
FROM ubuntu
RUN echo "Hello World"
HTTP_PROXY變量不會輸出到docker history,也不會被緩存。如果代理服務器變成了http://user:pass@proxy.sfo.example.com,后續的build不會導致cache miss。
可以使用ARG來覆蓋這個默認行為,
FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"
當build這個Dockerfile的時候,HTTP_PROXY會存到docker history中,如果它的值改變了,會把build緩存禁用掉。
對緩存的影響
ARG變量並不會像ENV持久化到image,但是會以類似的方式,影響到build緩存。如果Dockerfile定義了一個ARG變量,這個變量和前一個build不一樣,那么在第一次用這個變量的時候會發生"cache miss"(不是定義的時候)。尤其是,所有ARG后面的RUN指令一般都會使用ARG變量,這樣就會導致cache miss。但是所有預定義ARGs是沒有影響cache的,除非是在Dockerfile中有一個同名的ARG指令。
示例,2個Dockerfile
FROM ubuntu
ARG CONT_IMG_VER
RUN echo $CONT_IMG_VER
FROM ubuntu
ARG CONT_IMG_VER
RUN echo hello
如果在命令行指定--build-arg CONT_IMG_VER=<value>,以上2個示例在第2行都不會cache miss,第3行會cache miss。ARG CONT_IMG_VER會導致RUN那一行被認為是執行了CONT_IMG_VER=<value> echo hello,所以如果<value>改變了,就cache miss了。
另外一個示例,
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER $CONT_IMG_VER
RUN echo $CONT_IMG_VER
第3行會發生cache miss。因為ENV引用的ARG變量通過命令行改變了。另外,在這個示例中,ENV會導致image包含這個value(ENV會持久化到image中)。
如果ENV和ARG指令重復,
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER hello
RUN echo $CONT_IMG_VER
第3行就不會發生cache miss,因為CONT_IMG_VER的值是常量(hello)。因此第4行RUN指令用到的環境變量和值在build之間不會改變。
ONBUILD
ONBUILD <INSTRUCTION>
ONBUILD指令會在image中添加一個trigger,這個trigger會在image作為base的時候觸發。trigger會在下游的 build context中執行,就像在下游的Dockerfile 中,在 FROM指令之后,它就已經被立即嵌入了。
任何build指令都可以注冊為trigger。
如果你build一個image,這個image會作為base來build其他images,這就很有用。比如,一個應用build環境或者一個deamon自定義配置。
示例,如果一個image是可復用的Python應用builder(用來build新的應用image),那么它需要把應用源碼添加到一個特定目錄,然后調用build腳本。此時ADD和RUN指令是無法訪問應用源碼的,每個應用build的源碼也可能不一樣。你可以簡單地,給應用開發者提供Dockerfile樣本文件來復制粘貼到他們的應用中,但這是低效、易出錯和困難去做更新的,因為這個和“應用定義”代碼混淆了。
可以使用ONBUILD指令來提前注冊指令,在下個build stage再運行。
過程如下,
- 當碰到
ONBUILD指令,builder就會添加trigger到正在build的image的metadata。這條指令不會影響當前build。 - 在build的最后,所有的triggers都會被存儲到image的manifest,在key
OnBuild下面。可以用docker inspect命令查看。 - 然后image可能會被用來作為新build的base,使用
FROM指令。FROM指令在處理時,下游builder會查找ONBUILDtriggers,然后按它們注冊的順序執行。如果有trigger失敗了,FROM指令就會中斷,build失敗。如果triggers都成功了,那么FROM會完成,build成功。 - Triggers會在執行后,從最后一個image中清除。也就是說,它們是不會隨着“父子”build繼承的。
比如你可能會添加這樣的內容,
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
注意,1.鏈式ONBUILD ONBUILD是不允許的。2.ONBUILD可能不會trigger FROM 或 MAINTAINER指令。
STOPSIGNAL
STOPSIGNAL signal
STOPSIGNAL指令設置system call signal,發送到container退出。signal可以是有效的unsigned number(匹配kernel’s syscall table里的position,比如9),也可以是SIGNAME(比如SIGKILL)。
HEALTHCHECK
2種格式,
HEALTHCHECK [OPTIONS] CMD command(通過運行container里面的命令來檢查container)HEALTHCHECK NONE(禁用健康檢查,從base image繼承)
HEALTHCHECK 指令用來告訴Docker怎樣測試container是否還在工作。比如雖然server一直在運行,但是實際上已經死循環了,無法處理新連接了。
當container定義了健康檢查,就會把健康狀態添加到status中。status初始化是starting。無論健康檢查什么時候通過,它都會變為healthy(無論之前是什么狀態)。在一定數量的連續失敗后,它會變為unhealthy。
第一種格式的OPTION可以是,
--interval=DURATION(default:30s)--timeout=DURATION(default:30s)--start-period=DURATION(default:0s)--retries=N(default:3)
在container開始后的interval seconds ,會運行健康檢查。每個健康檢查完成后,等待interval seconds再次運行。
如果健康檢查運行的時候超過了timeout seconds,就認為失敗。
失敗的次數如果達到了retries的值,就認為unhealthy。
start period指定了container需要啟動的時間。在這期間探針失敗(Probe failure)不會記作重試次數。但是,如果在這期間健康檢查通過了,那么container就認為已經啟動了,這之后的失敗(all consecutive failures)就會記作重試次數。
一個Dockerfile只能有一個HEALTHCHECK 指令。如果有多個,那么只有最后一個HEALTHCHECK 生效。
第1種格式的command既可以是shell命令(如,HEALTHCHECK CMD /bin/check-running),也可以是exec數組。
command的退出狀態反應了container的健康狀態,
- 0: success - the container is healthy and ready for use
- 1: unhealthy - the container is not working correctly
- 2: reserved - do not use this exit code
示例,每5分鍾檢查1次,以確保web服務器能在3秒內為網站首頁提供服務,
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
為了幫助debug失敗探針(failing probes),任何寫到stdout或stderr輸出文本(UTF-8編碼)都會被存儲到健康狀態,並且可以使用docker inspect查詢。而且輸出應該簡短(目前只有最開始的4096 bytes會被存儲)。
當container的健康狀態改變了,會用新的狀態生成一個health_status事件。
SHELL
SHELL ["executable", "parameters"]
SHELL指令允許重寫shell格式命令的默認shell。Linux的默認shell是["/bin/sh", "-c"],Windows的默認shell是["cmd", "/S", "/C"]。SHELL指令必須在Dockfile中寫成JSON格式。
SHELL指令在Windows特別有用,因為Windows有2個常用的不同的原生shell,cmd和powershell,也有可選用的shell,包括sh。
SHELL指令可以出現多次。每個SHELL指令會覆蓋所有之前的SHELL指令,影響隨后的指令。
示例,
FROM microsoft/windowsservercore
# Executed as cmd /S /C echo default
RUN echo default
# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default
# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello
# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello
當shell格式的RUN, CMD,ENTRYPOINT出現在Dcokerfile中時,SHELL指令能影響這些指令。
示例,Windows上常見的模式,可以通過使用SHELL指令進行簡化,
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
docker調用的命令,
cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
這個有點低效,有2個原因。首先,有一個不必要的cmd.exe命令行處理器(aka shell)被調用了。其次,shell格式的RUN指令需要額外的前綴命令powershell -command。
為了更高效,有2種機制。其一是使用JSON格式,
RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
JSON格式是清晰的,不會使用不必要的cmd.exe。但是需要雙引號和轉義符,顯得有點冗余。
。其二是用SHELL指令和shell格式,這樣可以給Windows用戶更自然的語法,特別是和escape parser directive結合使用的時候,
# escape=`
FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'
結果是,
PS E:\docker\build\shell> docker build -t shell .
Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
---> Running in 6fcdb6855ae2
---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
---> Running in d0eef8386e97
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 10/28/2016 11:26 AM Example
---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
---> Running in be6d8e63fe75
hello world
---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\docker\build\shell>
SHELL指令也能被用來修改shell操作方式。比如在Windows用SHELL cmd /S /C /V:ON|OFF,可以修改延遲環境變量擴展語義。
SHELL指令也可以用在Linux上,可選的shell有zsh, csh, tcsh等。
Dockerfile示例
# Nginx
#
# VERSION 0.0.1
FROM ubuntu
LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0"
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
# Firefox over VNC
#
# VERSION 0.3
FROM ubuntu
# Install vnc, xvfb in order to create a 'fake' display and firefox
RUN apt-get update && apt-get install -y x11vnc xvfb firefox
RUN mkdir ~/.vnc
# Setup a password
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
# Autostart firefox (might not be the best way, but it does the trick)
RUN bash -c 'echo "firefox" >> /.bashrc'
EXPOSE 5900
CMD ["x11vnc", "-forever", "-usepw", "-create"]
# Multiple images example
#
# VERSION 0.1
FROM ubuntu
RUN echo foo > bar
# Will output something like ===> 907ad6c2736f
FROM ubuntu
RUN echo moo > oink
# Will output something like ===> 695d7793cbe4
# You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
# /oink.
以下內容可查看參考資料進一步閱讀。
- BuildKit(第三方工具)
- Parser directives的命令syntax(依賴BuildKit)
- RUN已知bug(Issue 783)
- External implementation features(依賴BuildKit)
- Automatic platform ARGs in the global scope(依賴BuildKit)
參考資料
https://docs.docker.com/engine/reference/builder/
下一篇《Dockerfile最佳實踐》,歡迎持續關注哦。
