在Dockerfile中,指令不區(qū)分大小寫(xiě),但是為了與參數(shù)區(qū)分,推薦大寫(xiě)。Docker會(huì)順序執(zhí)行Dockerfile中的指令,第一條指令必須是FROM指令,它用于構(gòu)建鏡像的基礎(chǔ)鏡像。在Dockerfile中以#開(kāi)頭的行是注釋?zhuān)谄渌恢贸霈F(xiàn)的#會(huì)被當(dāng)成參數(shù)。
Dockerfile中的指令有FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADD、COPY、ENTRYPOINT、VOLUME、USER、WORKDIR、ONBUILD,錯(cuò)誤的指令會(huì)被忽略,下面將詳細(xì)講解一些重要的Docker指令。
- ENV
格式: ENV <key> <value>或ENV <key>=<value> ...
ENV指令可以為鏡像創(chuàng)建出來(lái)的容器聲明環(huán)境變量。并且在Dockerfile中,ENV指令聲明的環(huán)境變量會(huì)被后面的特定指令(即ENV、ADD、COPY、WORKDIR、EXPOSE、VOLUME、USER)解釋使用。其他指令使用環(huán)境變量時(shí),使用格式為$variable_name或者${variable_name}。在變量前面添加斜線\可以轉(zhuǎn)義。另外,ONBUILD指令不支持環(huán)境變量。
- FROM
格式: FROM <image> 或 FROM <image>:<tag>
FROM指令的功能是為后面的指令提供基礎(chǔ)鏡像,因此一個(gè)有效的Dockerfile必須以FROM指令作為第一條非注釋指令。在一個(gè)Dockerfile中,F(xiàn)ROM指令可以出現(xiàn)多次,這樣會(huì)構(gòu)建多個(gè)鏡像。
- COPY
格式:COPY <src> <desc>
COPY指令復(fù)制<src>所指向的文件或目錄,將它添加到新鏡像中,復(fù)制的文件或目錄在鏡像中的路徑是<dest>。<src>所指定的源可以有多個(gè),但必須在上下文中,即必須是上下文根目錄的相對(duì)路徑。不能使用形如COPY ../something /something這樣的指令。此外,<src>可以使用通配符指向所有匹配通配符的文件或目錄,例如,COPY hom* /mydir/表示添加所有以"hom"開(kāi)頭的文件到目錄/mydir/中。
<dest>可以是文件或目錄,但必須是目標(biāo)鏡像中的絕對(duì)路徑或者相對(duì)于WORKDIR的相對(duì)路徑(WORKDIR即Dockerfile中WORKDIR指令指定的路徑,用來(lái)為其他指令設(shè)置工作目錄)。若<dest>以反斜線/結(jié)尾則其指向的是目錄;否則指向文件。<src>同理。若<dest>是一個(gè)文件,則<src>的內(nèi)容會(huì)被寫(xiě)入到<dest>中;否則<src>指向的文件或目錄中的內(nèi)容會(huì)被復(fù)制添加到<dest>目錄中。當(dāng)<src>指定多個(gè)源時(shí),<dest>必須是目錄。另外,如果<dest>不存在,則路徑中不存在的目錄會(huì)被創(chuàng)建。
- ADD
格式:ADD <src> <dest>
ADD與COPY指令在功能上很相似,都支持復(fù)制本地文件到鏡像的功能,但ADD指令還支持其他功能。<src>可以是一個(gè)指向網(wǎng)絡(luò)文件的URL,此時(shí)若<dest>指向一個(gè)目錄,則URL必須是完全路徑,這樣可以獲取該網(wǎng)絡(luò)文件的文件名filename,該文件名被復(fù)制添加到<dest>/<filename>。例如,ADD http://example.com/foobar / 會(huì)創(chuàng)建文件/foobar。
<src>還可以指向一個(gè)本地壓縮歸檔文件,該文件在復(fù)制到容器中時(shí)會(huì)被解壓提取,如ADD example.tar.xz /。但若URL中的文件為歸檔文件則不會(huì)被解壓提取。
ADD和COPY指令雖然功能相似,但一般推薦使用COPY,因?yàn)镃OPY只支持本地文件,相比ADD而言,它更透明。
- RUN
RUN的格式有兩種
RUN <command> (shell格式)
RUN ["executable", "param1", "param2"](exec格式,推薦格式)
RUN指令會(huì)在前一條命令創(chuàng)建出的鏡像基礎(chǔ)上創(chuàng)建一個(gè)容器,并在容器中運(yùn)行命令,在命令結(jié)束運(yùn)行后提交容器為新鏡像,新鏡像被Dockerfile中的下一條指令使用。
RUN指令的兩種格式表示命令在容器中的兩種運(yùn)行方式。當(dāng)使用shell格式時(shí),命令通過(guò)/bin/sh -c運(yùn)行;當(dāng)使用exec格式時(shí),命令是直接運(yùn)行的,容器不調(diào)用shell程序,及容器中沒(méi)有shell程序。exec格式中的參數(shù)會(huì)當(dāng)初JSON數(shù)組被Docker解析,故需要使用雙引號(hào)而不能使用單引號(hào)。因?yàn)閑xec格式不會(huì)再shell中執(zhí)行,所以環(huán)境變量的參數(shù)不會(huì)被替換,例如,當(dāng)執(zhí)行RUN ["echo", "$HOME"]指令時(shí),$HOME不會(huì)做變量替換。如果希望運(yùn)行shell程序,指令可以寫(xiě)成RUN ["sh", "-c", "echo", "$HOME"]。
- CMD
CMD指令有三種格式:
CMD <command> (shell格式)
CMD ["executable", "param1", "param2"] (exec格式,推薦格式)
CMD ["param1", "param2"] (為ENTRYPOINT指令提供參數(shù))
CMD指令提供容器運(yùn)行時(shí)的默認(rèn)值,這些默認(rèn)值可以是一條指令,也可以是一些參數(shù)。一個(gè)Dockerfile中可以有多條CMD指令,但只有最后一條CMD指令有效。CMD ["param1", "param2"]格式是在CMD指令和ENTRYPOINT指令配合時(shí)使用的,CMD指令中的參數(shù)會(huì)添加到ENTRYPOINT指令中。使用shell和exec格式時(shí),命令在容器中的運(yùn)行方式與RUN指令相同。不同在于,RUN指令在構(gòu)建鏡像時(shí)執(zhí)行命令,并生成新的鏡像;CMD指令在構(gòu)建鏡像時(shí)不執(zhí)行任何命令,而是在容器啟動(dòng)是默認(rèn)將CMD指令作為第一條執(zhí)行的命令。如果用戶在命令行界面運(yùn)行docker run命令時(shí)指定了命令參數(shù),則會(huì)覆蓋CMD指令中的命令。
- ENTRYPOINT
ENTRYPOINT指令有兩種格式:
ENTRYPOINT <command> (shell格式)
ENTRYPOINT ["executable", "param1", "param2"] (exec格式,推薦格式)
ENTRYPOINT指令和CMD指令類(lèi)似,都可以讓容器在每次啟動(dòng)時(shí)執(zhí)行相同的指令,但它們之間又有不同。一個(gè)Dockerfile中可以有多條ENTRYPOINT指令,但只有最后一條ENTRYPOINT指令有效。當(dāng)使用shell格式時(shí),ENTRYPOINT指令會(huì)忽略CMD指令和docker run命令的參數(shù),并且會(huì)運(yùn)行在/bin/sh -c中。這意味著ENTRYPOINT指令進(jìn)程為/bin/sh -c的子進(jìn)程,進(jìn)程在容器中國(guó)的PID將不是1,且不能接受Unix信號(hào)。即當(dāng)使用docker stop <container>命令時(shí),命令進(jìn)程接收不到SIGTERM信號(hào)。我們推薦使用exec格式,使用此格式時(shí),docker run傳入的命令參數(shù)會(huì)覆蓋CMD指令的內(nèi)容并且附加到ENTRYPOINT指令的參數(shù)中。從ENTRYPOINT的使用中可以看出,CMD可以是參數(shù),也可以是指令,而ENTRYPOINT只能是命令;另外docker run命令提供的命令行參數(shù)可以覆蓋CMD,但不能覆蓋ENTRYPOINT。
- ONBUILD
格式: ONBUILD [INSTRUCTION]
ONBUILD指令的功能是添加一個(gè)將來(lái)執(zhí)行的觸發(fā)器指令到鏡像中。當(dāng)該鏡像作為FROM指令的參數(shù)時(shí),這些觸發(fā)器命令就會(huì)在FROM指令執(zhí)行時(shí)加入到構(gòu)建過(guò)程中。盡管任何指令都可以注冊(cè)成一個(gè)觸發(fā)器指令,但ONBUILD指令中不能包含ONBUILD指令,并且不會(huì)觸發(fā)FROM和MAINTAINER指令。當(dāng)需要制作一個(gè)基礎(chǔ)鏡像來(lái)構(gòu)建其他鏡像時(shí),ONBUILD是很有用的。例如,當(dāng)需要構(gòu)建的鏡像是一個(gè)可重復(fù)使用的Python環(huán)境鏡像時(shí),它可能需要將應(yīng)用源代碼加入到一個(gè)指定目錄中,還可能需要執(zhí)行一個(gè)構(gòu)建腳本。此時(shí)不能僅僅調(diào)用ADD和RUN指令,因?yàn)楝F(xiàn)在還不能訪問(wèn)應(yīng)用源代碼。
并且不同應(yīng)用的源代碼是不同的。我們不能簡(jiǎn)單地提供一個(gè)Dockerfile模板給應(yīng)用開(kāi)發(fā)者,它與特定應(yīng)用代碼耦合,會(huì)引發(fā)低效、易錯(cuò)、難以更新等問(wèn)題。這些場(chǎng)景的解決方案是使用ONBUILD指令注冊(cè)觸發(fā)器指令,利用ONBUILD指令構(gòu)建一個(gè)語(yǔ)言棧鏡像,該鏡像可以構(gòu)建任何用語(yǔ)言編寫(xiě)的用戶軟件的鏡像。
ONBUILD指令的具體執(zhí)行步驟如下:
- 在構(gòu)建過(guò)程中,ONBUILD指令會(huì)添加到觸發(fā)器指令鏡像元數(shù)據(jù)中。這些觸發(fā)器指令不會(huì)在當(dāng)前構(gòu)建過(guò)程中執(zhí)行。
- 在構(gòu)建過(guò)程最后,觸發(fā)器指令會(huì)被存儲(chǔ)在鏡像詳情中,其主鍵是OnBuild,可以使用docker inspect命令查看。
- 之后鏡像可能作為其他Dockerfile中FROM指令的參數(shù)。在構(gòu)建過(guò)程中,F(xiàn)ROM指令會(huì)尋找ONBUILD觸發(fā)器指令,并且會(huì)以他們注冊(cè)的順序執(zhí)行。若有觸發(fā)器指令執(zhí)行失敗,則FROM指令被終止,并返回失??;若所有觸發(fā)器指令執(zhí)行成功,則FROM指令完成并繼續(xù)執(zhí)行下面的指令。在鏡像構(gòu)建完成后,觸發(fā)器指令會(huì)被清除,不會(huì)被子孫鏡像繼承。
使用包含ONBUILD指令的Dockerfile構(gòu)建的鏡像應(yīng)該有特殊的標(biāo)簽,如ruby:2.0-onbuild。在ONBUILD指令中添加ADD或COPY指令時(shí)要額外注意。假如新構(gòu)建過(guò)程的上下文缺失了被添加的資源,那么新構(gòu)建過(guò)程會(huì)失敗。給ONBUILD鏡像添加標(biāo)簽,可以提示編寫(xiě)Dockerfile的開(kāi)發(fā)人員小心應(yīng)對(duì)。