Dockerfile
是一個文本文件,包含一些Docker指令。執行docker build
,Docker就會執行Dockerfile
裏面的指令,來自動建立鏡像。python
Dockerfile裏面的指令能夠訪問context這些文件。linux
context是遞歸的,PATH
包含全部子目錄,URL
包含全部子模塊。nginx
例子,把當前目錄當作context,git
$ docker build . Sending build context to Docker daemon 6.51 MB ...
build是由Docker daemon(守護進程)來運行,而不是CLI。github
build會把整個context發給daemon。因此最好把context設置爲空目錄,把Dockerfile放進去。只添加須要的文件,爲了提升build性能,還能夠添加.dockerignore
來排除一些文件和目錄。golang
Warning!不要用系統根目錄/
做爲PATH,否則會把根目錄下全部東西都傳給Docker daemon。web
通常會把Dockerfile放在context根目錄下,也可使用-f
來指定其餘路徑,docker
$ docker build -f /path/to/a/Dockerfile .
指定鏡像存放倉庫可使用-t
,shell
$ docker build -t shykes/myapp .
支持多個,apache
$ 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"
# 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=`
用來指定轉義符。這個在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}
set variable
後就是set的值,沒有set variable
值就是word
${variable:+word}
set variable
後值就是word
,沒有set variable
就是空字符串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的一下指令都支持環境變量
ADD
COPY
ENV
EXPOSE
FROM
LABEL
STOPSIGNAL
USER
VOLUME
WORKDIR
ONBUILD
(結合以上指令使用)須要注意的是,變量替換是針對整條指令的,
ENV abc=hello ENV abc=bye def=$abc ENV ghi=$abc
def
的值是hello,而不是bye,由於上一條指令賦值的hello。
ghi
的值纔會是bye。
.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
指令初始化一個新的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 <command>
(shell 格式,Linux /bin/sh -c
Windowscmd /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
和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 <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
已經棄用了,直接使用LABLE
,
LABEL maintainer="SvenDowideit@home.org.au"
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 <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 [--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,就只能用ADD
URL。也能夠在使用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
和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
指令的緩存。
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
纔會生效。
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
定義一個簡單的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
指令都定義了運行container時,哪些命令會執行。他們的結合有一些規則,
CMD
或ENTRYPOINT
。ENTRYPOINT
。ENTRYPOINT
定義默認參數,或者在container中執行ad-hoc(臨時)命令,應該使用CMD
。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 ["/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。
遵循規則,
C:
之外的驅動VOLUME
指令也不支持host-dir
這樣的參數。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 /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 <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
或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指令的一部分指令。
ADD
COPY
ENV
EXPOSE
FROM
LABEL
STOPSIGNAL
USER
VOLUME
WORKDIR
ONBUILD
(結合以上指令使用)Docker有一些預約義的ARG
變量,你能夠不使用ARG
指令,直接用這些變量。
HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_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 <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。OnBuild
下面。能夠用 docker inspect
命令查看。FROM
指令。 FROM
指令在處理時,下游builder會查找 ONBUILD
triggers,而後按它們註冊的順序執行。若是有trigger失敗了,FROM
指令就會中斷,build失敗。若是triggers都成功了,那麼FROM
會完成,build成功。好比你可能會添加這樣的內容,
ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src
注意,1.鏈式ONBUILD ONBUILD
是不容許的。2.ONBUILD
可能不會trigger FROM
或 MAINTAINER
指令。
STOPSIGNAL signal
STOPSIGNAL
指令設置system call signal,發送到container退出。signal能夠是有效的unsigned number(匹配kernel’s syscall table裏的position,好比9),也能夠是SIGNAME(好比SIGKILL)。
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的健康狀態,
示例,每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 ["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
等。
# 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.
如下內容可查看參考資料進一步閱讀。
參考資料
https://docs.docker.com/engine/reference/builder/
下一篇《Dockerfile最佳實踐》,歡迎持續關注哦。
版權申明:本文爲博主原創文章,轉載請保留原文連接及做者。
專一測試,堅持原創,只作精品。