
在上面的內(nèi)容中介紹了如何使用docker commit的方法來構(gòu)建鏡像,相反推薦使用被稱為Dockerfile的定義文件和docker build命令來構(gòu)建鏡像。Dockerfile使用基本的基于DSL語法的指令來構(gòu)建一個Docker鏡像,之后使用docker build命令基于該Dockerfile中的指令構(gòu)建一個新的鏡像。
(一)第一個Dockerfile
現(xiàn)在來創(chuàng)建一個最簡單的Dockerfile文件樣例,先創(chuàng)建一個空的Dockerfile文件,在任意目錄下都行,在Dockerfile文件中填入以下內(nèi)容。
FROM alpine:3.14
//設(shè)置容器內(nèi)的數(shù)據(jù)卷
VOLUME ["/var/html"]
EXPOSE 80
該Dockerfile由一系列指令和參數(shù)組成,每條指令,如FROM,都必須為大寫字母且后面跟隨一個參數(shù):FROM alpine:3.14,Dockerfile中的指令會按順序從上到下執(zhí)行,所以應(yīng)該根據(jù)需要合理安排指令的順序。
使用build命令構(gòu)建鏡像的步驟如下 :

第一步:Docker從基礎(chǔ)鏡像運行一個容器。
第二步:執(zhí)行數(shù)據(jù)卷指令來創(chuàng)建一個數(shù)據(jù)卷。
第三步:設(shè)置訪問端口
—— 最后所有指令執(zhí)行完畢。
每個Dockerfile的第一條指令都應(yīng)該是FROM,F(xiàn)ROM指令指定一個已經(jīng)存在的鏡像后續(xù)指令都將基于該鏡像進行,這個鏡像被稱為基礎(chǔ)鏡像(base iamge)。在上面的Dockerfile示例中,我們指定了alpine:3.14作為鏡像的基礎(chǔ)鏡像,基于這個Dockerfile構(gòu)建的新鏡像將以alpine:3.14操作系統(tǒng)為基礎(chǔ),在運行一個容器時,必須要指明是基于哪個基礎(chǔ)鏡像在進行構(gòu)建。
接著是VOLUME指令,用于指定數(shù)據(jù)卷的設(shè)置。
最后設(shè)置EXPOSE指令,這條指令告訴Docker該容器內(nèi)的應(yīng)用程序?qū)褂萌萜鞯闹付ǘ丝冢@并不意味著可以自動訪問任意容器運行中服務(wù)的端口,這里指定的是80端口。
(二)Dockerfile指令
本小節(jié)主要針對Dockerfile文件中使用到的指令進行介紹,介紹Dockerfiler文件中常用的指令,在Dockerfile文件中所有的指令都必須使用大寫。
1.FROM
FROM指令的格式如下:
FROM [--platform=<platform>] <image> [AS <name>]
OR
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
OR
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
FROM指令表示初始化一個新構(gòu)建并作為后續(xù)指令的基本映像,所以一個有效的Dockerfile文件必須以FROM指令開始,為個鏡像也必須是一個有效鏡像,可以從一些公用庫中來獲取相應(yīng)的鏡像。
但ARG可以放在FROM指令前面,關(guān)于ARG和FROM如何交互將會在后面介紹。
FROM可以在單個Dockerfile中多出現(xiàn),這樣可以創(chuàng)建多個映像,或?qū)⒁粋€構(gòu)建階段用作另一個構(gòu)建階段的依賴庫。
FROM指令可以支持由其上一個指令ARG指令聲明的變量,并傳遞給FROM指令使用。
ARG CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app
FROM extras:${CODE_VERSION}
CMD /code/run-extras
2.RUN
RUN指令有兩種運行方式:
#shell格式:
RUN <command>
#exec格式:
RUN ["executable", "param1", "param2"]
# 例如:
# RUN ["./test.php", "dev", "offline"] 等價于 RUN ./test.php dev offline
注意:Dockerfile的指令每執(zhí)行一次都會在docker上新建一層,所以過多無意義的層,會造成鏡像膨脹過大。

如上,以 && 符號連接命令,這樣執(zhí)行后,只會創(chuàng)建 1 層鏡像。
3.CMD
CMD指令用于指定一個容器啟動時所運行的命令,這有點類似于RUN指令,但RUN指令是指定鏡像被構(gòu)建時要運行的命令,而CMD是指容器啟動時所要運行的命令,CMD指令有三種形式。
#exec格式 CMD ["executable","param1","param2"]
# 該寫法是為 ENTRYPOINT 指令指定的程序提供默認參數(shù) CMD ["param1","param2"]
#shell格式 CMD command param1 param2
一般推薦使用第一種格式,執(zhí)行過程清晰明確。第三種格式其實在運行過程中也會轉(zhuǎn)換成第一種格式運行,并且默認可執(zhí)行文件是sh。
CMD和使用docker run命令啟動容器時指定運行命令幾乎一致。
#如在Dockerfile文件中寫以下指令
CMD ["/bin/true"]
#上面這個CMD等同以下面的run指令
root@ubunhomhome/test# docker run -it nginx /bin/true
在Dockerfile中只能有一個CMD指令,即使如果有多個CMD指令,也只有最后一個CMD才會生效。
CMD指令主要是為執(zhí)行中的容器提供默認值,這些默認值可以包含可執(zhí)行文件,也可以省略可執(zhí)行文件,在這種情況下,就必須指定一條ENTRYPOINT指令。
如果用戶在使用docker run指定參數(shù)時,則它們將會覆蓋Dockerfile文件中的CMD參數(shù)默認值。
4.LABEL
LABEL指令用來給鏡像添加一些元數(shù)據(jù)(metadata),以鍵值對的形式,語法格式如下:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
如果LABEL值中需要跨多行,則需要加入反斜杠和引號。

一個鏡像可以有多個標簽,可以在一行上指定多個標簽,有以下兩種方式可以實現(xiàn)。

5.EXPOSE
該指令用于聲明監(jiān)聽的端口號,在監(jiān)聽是可以指定是TCP還是UDP協(xié)議,默認值為TCP,其主要有以下作用:
幫助鏡像使用理解這個鏡像服務(wù)的守護端口,以方便配置映射。
在運行時使用隨時端口映射,也就是docker run -P時,會自動隨機映射EXPOSE的端口。
EXPOSE指令實際上并未發(fā)布指定端口,它是將我們訪問時輸入的端口和運行容器之間做一種關(guān)聯(lián)或通信,具體發(fā)布哪些端口,要在運行容器時發(fā)布實際端口,可以使用docker run中-p或-P來設(shè)置映射的端口號。
默認情況下一般如果我們未指定協(xié)議的話,那么都是使用TCP協(xié)議,當然也可以指定具體的協(xié)議,也可以同時指定TCP和UDP協(xié)議。
EXPOSE 80/tcp
EXPOSE 80/udp
6.ENV
設(shè)置環(huán)境變量,定義了環(huán)境變量,這樣在后續(xù)的指令中,就可以使用這個環(huán)境變量。如果在環(huán)境變量引用過程中包含空格,那么需要使用到反斜杠。
其語法格式如下:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
使用ENV設(shè)置的環(huán)境變量會保留下來,當容器運行時可以使用docker inspect查看值,并且可以使用docker run --env = 更改環(huán)境變量的值。
如果僅僅是在構(gòu)建過程中需要環(huán)境變量,而在最終映像中不需要,可以考慮為單個命令設(shè)置一個值或使用ARG,ARG指令不會人保留在最終鏡像中。
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y ...
7.ARG
ARG構(gòu)建參數(shù)與ENV作用一致,不過作用域不一樣,ARG設(shè)置的環(huán)境變量僅對Dockerfile內(nèi)有效,也就是說只有docker build的過程中有效,構(gòu)建好的鏡像內(nèi)不存在此環(huán)境變量。
構(gòu)建命令docker build中可以用--build-arg <參數(shù)名>=<值>來覆蓋,ARG指令格式如下:
ARG <參數(shù)名>[=默認值]
如果ARG指令具有默認值,并且在構(gòu)建時未傳遞任何值,則構(gòu)建時會使用默認值,相反如果未設(shè)置默認值,并且在構(gòu)建中未傳遞任何值,那么構(gòu)建時會輸出警告信息。
8.ADD
ADD指令是指將宿主機的文件或目錄復制到鏡像文件系統(tǒng)中指定的路徑,其語法格式有兩種:
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
每個可以使用通配符,并且匹配將使用Go的filepath.Match規(guī)則進行,例如:
要添加所有以“hom”開頭的文件:
ADD hom* /mydir/
“?”可以替換任意一個字符,例如:
ADD hom?.txt /mydir/
是指構(gòu)建容器的路徑,可以是絕對路徑,也可以是相對路徑,但這個相對路徑是相對于WORKDIR的路徑。如以下示例是將“test.txt”文件添加到/relativeDir/。
ADD test.txt relativeDir/
下面的示例則是將“test.txt”添加到相對路徑/absoluteDie/中。
ADD test.txt /absoluteDir/
如果路徑中包含一些特字符,那么需要按照Golang規(guī)則轉(zhuǎn)義那些路徑,以防止在解析過程中將它們視為匹配的模式,如要添加名為sarr[1].txt的文件。
ADD sarr[[]1].txt /mydir/
路徑必須是構(gòu)建內(nèi)容的一個內(nèi)部路徑,不能添加類似于ADD ../path /path,因為docker build的第一步是將上下文目錄和子目錄發(fā)送到docker守護進程。
如果是URL,而不以斜杠結(jié)尾,則從URL下載文件并將其復制到。
如果是URL,而以斜杠結(jié)尾,則從URL推斷文件名,并將文件下載到/,例如ADD http://chuanshi.com/foobar /,將創(chuàng)建文件/foobar,該URL必須具有正確的路徑,以便在這種情況下可以找到適當?shù)奈募?/p>
如果是目錄,則復制目錄的整個內(nèi)容,包括文件系統(tǒng)元數(shù)據(jù)。
如果是任何其他類型的文件,則會將其及其元數(shù)據(jù)一起單獨復制,在這種情況下,如果以斜杠結(jié)尾,則它將被視為目錄,并且的內(nèi)容將被寫在/base()中。
如果直接或由于使用通配符而指定多個資源,則必須是目錄,并且必須以斜杠結(jié)尾。
如果不以斜杠結(jié)尾,則將其視為常規(guī)文件,并且的內(nèi)容將被寫入。
如果不存在,則會與路徑中所有缺少的目錄一起創(chuàng)建它。
1.ADD的優(yōu)點:在執(zhí)行為tar壓縮文件的話,壓縮格式為gzip、gzip2以及xz的情況下,會自動復制并解壓到。
2.ADD的缺點:在不解壓的前提下,無法復制tar壓縮文件,會令鏡像構(gòu)建緩存失效,從而可能會令鏡像構(gòu)建變得比較緩慢,具體是否使用,可以根據(jù)是否需要自動解壓來決定。
9.COPY
復制指令,從上下文目錄中復制文件或者目錄到容器里指定路徑,通常有以下兩種格式:
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
COPY指令表示從復制新文件或目錄,并將它們添加到容器的文件系統(tǒng)中,路徑為。
可以指定多個資源,但是文件和目錄的路徑將被解釋為相對于構(gòu)建上下文。
每個可能包含通配 符,并且匹配將合作Go人filepath.Match規(guī)則進行。
例如要添加所有"hom"開頭的文件:
COPY home* /mydir/
"?"表示可以匹配任意一個字符:
COPY hom?.txt /mydir/
是指構(gòu)建容器的路徑,可以是絕對路徑,也可以是相對路徑,但這個相對路徑是相對于WORKDIR的路徑。如以下示例是將“test.txt”文件添加到/relativeDir/。
COPY test.txt relativeDir/
下面的示例則是將“test.txt”添加到相對路徑/absoluteDie/中。
COPY test.txt /absoluteDir/
如果路徑中包含一些特字符,那么需要按照Golang規(guī)則轉(zhuǎn)義那些路徑,以防止在解析過程中將它們視為匹配的模式,如要添加名為sarr[1].txt的文件。
COPY sarr[[]1].txt /mydir/
路徑必須是構(gòu)建內(nèi)容的一個內(nèi)部路徑,不能添加類似于COPY ../path /path,因為docker build的第一步是將上下文目錄和子目錄發(fā)送到docker守護進程。
如果是目錄,則復制目錄的整個內(nèi)容,包括文件系統(tǒng)無數(shù)據(jù),目錄本身不會被復制,只是其內(nèi)容被復制。
如果是任何其他類型的文件,則會將其及其元數(shù)據(jù)一起單獨復制,在這種情況下,如果以斜杠結(jié)尾,則它將被視為目錄,并且的內(nèi)容將被寫在/base()中。
如果直接或由于使用通配符而指定多個資源,則必須是目錄,并且必須以斜杠結(jié)尾。
如果不以斜杠結(jié)尾,則將其視為常規(guī)文件,并且的內(nèi)容將被寫入。
如果不存在,則會與路徑中所有缺少的目錄一起創(chuàng)建它。
10.ENTRYPOINT
類似于CMD指令,但其不會被docker run指令運行參數(shù)所覆蓋,并且這些命令行參數(shù)會被當作參數(shù)送給ENTRYPOINT指令指定的程序。
但是如果運行docker run時使用了--entrypoint選項,將覆蓋CMD指令指定的程序。
其語法格式有以下兩種:
#exec格式
ENTRYPOINT ["executable", "param1", "param2"]
#shell格式
ENTRYPOINT command param1 param2
ENTRYPOINT的優(yōu)點在執(zhí)行docker run的時候可以指定ENTRYPOINT運行所需要的參數(shù)。
但如果Dockerfile中如果存在多個ENTRYPOINT指令時,只有最后一個會生效。
可以將ENTRYPOINT與CMD命令搭配使用,一般是變參才會使用CMD,這里的CMD等于是在給ENTRYPOINT傳參。
例如,通過Dockerfile構(gòu)建了一個nginx:V1.0的鏡像。
FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定參
CMD ["/etc/nginx/nginx.conf"] # 變參
如果在運行容器時,不傳參運行,
root@ubunhomhome/test# docker run -it nginx:v1.0
容器內(nèi)則會默認運行以下命令,啟動主進程。
nginx -c /etc/nginx/nginx.conf
如果在運行容器時傳參運行,
root@ubunhomhome/test# docker run -it nginx:v1.0 -c /etc/nginx/new_nginx.conf
容器內(nèi)會默認運行以下命令,啟動主進程。
nginx -c /etc/nginxnew_nginx.conf
11.VOLUME
VOLUME指令在后面的章節(jié)會進行詳細的介紹,在本小節(jié)中不做介紹
12.USER
USER指令用于指定執(zhí)行后續(xù)命令的用戶和用戶組,這邊只是切換后續(xù)指令執(zhí)行的用戶(用戶和用戶組必須提前已經(jīng)存在)。其語法格式如下:
USER <user>[:<group>]
#or
USER <UID>[:<GID>]
13.WORKDIR
WORKDIR指令表示指定工作目錄,該指定的目錄必須提前創(chuàng)建好,docker build構(gòu)建鏡像過程中的,每個RUN命令都是新建的一層,只有通過WORKDIR創(chuàng)建才會一直存在。其語法格式如下:
WORKDIR /path/to/workdir
WORKDIR指令可在Dockerfile中多次使用,如果提供了相對路徑,則它將相對于上一個WORKDIR指令的路徑來創(chuàng)建目錄,如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
該Dockerfile中最后一個pwd命令的輸出為/a/b/c。
WORKDIR指令也可以解析ENV設(shè)置的環(huán)境變量,如:
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
該Dockerfile中最后一個pwd命令的輸出為/path/$DIRNAME。