上次的推文內容中介紹了如何使用docker commit的方法來構建鏡像,相反推薦使用被稱為Dockerfile的定義文件和docker build命令來構建鏡像。Dockerfile使用基本的基于DSL語法的指令來構建一個Docker鏡像,之后使用docker build命令基于該Dockerfile中的指令構建一個新的鏡像。
一、第一個Dockerfile
現在來創建一個最簡單的Dockerfile文件樣例,先創建一個空的Dockerfile文件,在任意目錄下都行,在Dockerfile文件中填入以下內容:
FROM alpine:3.14
//設置容器內的數據卷
VOLUME ["/var/html"]
EXPOSE 80
該Dockerfile由一系列指令和參數組成,每條指令,如FROM,都必須為大寫字母且后面跟隨一個參數:FROM alpine:3.14,Dockerfile中的指令會按順序從上到下執行,所以應該根據需要合理安排指令的順序。
使用build命令構建鏡像的步驟如下 :???????
root@ubuntu:/home/test# docker build -t nginx:v1.0 .
Sending build context to Docker daemon 8.65MB
Step 1/3 : FROM alpine:3.14
3.14: Pulling from library/alpine
a0d0a0d46f8b: Pull complete
Digest: sha256:e1c082e3d3c45cccac829840a25941e679c25d438cc8412c2fa221cf1a824e6a
Status: Downloaded newer image for alpine:3.14
---> 14119a10abf4
Step 2/3 : VOLUME ["/var/html"]
---> Running in baa52a8c37bd
Removing intermediate container baa52a8c37bd
---> 83ba81539ce7
Step 3/3 : EXPOSE 80
---> Running in b0b0efd15aa3
Removing intermediate container b0b0efd15aa3
---> d10277ba6308
Successfully built d10277ba6308
Successfully tagged nginx:v1.0
第一步:Docker從基礎鏡像運行一個容器。
第二步:執行數據卷指令來創建一個數據卷。
第三步:設置訪問端口
最后所有指令執行完畢。
每個Dockerfile的第一條指令都應該是FROM,FROM指令指定一個已經存在的鏡像后續指令都將基于該鏡像進行,這個鏡像被稱為基礎鏡像(base iamge)。在上面的Dockerfile示例中,我們指定了alpine:3.14作為鏡像的基礎鏡像,基于這個Dockerfile構建的新鏡像將以alpine:3.14操作系統為基礎,在運行一個容器時,必須要指明是基于哪個基礎鏡像在進行構建。
接著是VOLUME指令,用于指定數據卷的設置。
最后設置EXPOSE指令,這條指令告訴Docker該容器內的應用程序將會使用容器的指定端口,這并不意味著可以自動訪問任意容器運行中服務的端口,這里指定的是80端口。
二、Dockerfile指令
這里主要針對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指令表示初始化一個新構建并作為后續指令的基本映像,所以一個有效的Dockerfile文件必須以FROM指令開始,為個鏡像也必須是一個有效鏡像,可以從一些公用庫中來獲取相應的鏡像。
但ARG可以放在FROM指令前面,關于ARG和FROM如何交互將會在后面介紹。
FROM可以在單個Dockerfile中多出現,這樣可以創建多個映像,或將一個構建階段用作另一個構建階段的依賴庫。
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的指令每執行一次都會在docker上新建一層,所以過多無意義的層,會造成鏡像膨脹過大。???????
FROM centos
RUN yum install wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN tar -xvf redis.tar.gz
以上執行會創建 3 層鏡像。可簡化為以下格式:
FROM centos
RUN yum install wget \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-
5.0.3.tar.gz" \
&& tar -xvf redis.tar.gz
如上,以 && 符號連接命令,這樣執行后,只會創建 1 層鏡像。
3.CMD
CMD指令用于指定一個容器啟動時所運行的命令,這有點類似于RUN指令,但RUN指令是指定鏡像被構建時要運行的命令,而CMD是指容器啟動時所要運行的命令,CMD指令有三種形式。???????
#exec格式
CMD ["executable","param1","param2"]
# 該寫法是為 ENTRYPOINT 指令指定的程序提供默認參數
CMD ["param1","param2"]
#shell格式
CMD command param1 param2
一般推薦使用第一種格式,執行過程清晰明確。第三種格式其實在運行過程中也會轉換成第一種格式運行,并且默認可執行文件是sh。
CMD和使用docker run命令啟動容器時指定運行命令幾乎一致。???????
#如在Dockerfile文件中寫以下指令
CMD ["/bin/true"]
#上面這個CMD等同以下面的run指令
root@ubunhomhome/test# docker run -it nginx /bin/true
在Dockerfile中只能有一個CMD指令,即使如果有多個CMD指令,也只有最后一個CMD才會生效。
CMD指令主要是為執行中的容器提供默認值,這些默認值可以包含可執行文件,也可以省略可執行文件,在這種情況下,就必須指定一條ENTRYPOINT指令。
如果用戶在使用docker run指定參數時,則它們將會覆蓋Dockerfile文件中的CMD參數默認值。
4.LABEL
LABEL指令用來給鏡像添加一些元數據(metadata),以鍵值對的形式,語法格式如下:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
如果LABEL值中需要跨多行,則需要加入反斜杠和引號。???????
LABEL "com.example.vendor"="chuanshi"
LABEL com.example.label-with-value="test"
LABEL version="3.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
一個鏡像可以有多個標簽,可以在一行上指定多個標簽,有以下兩種方式可以實現。???????
LABEL
Learn more about the "LABEL" Dockerfile command.
multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
5.EXPOSE
該指令用于聲明監聽的端口號,在監聽是可以指定是TCP還是UDP協議,默認值為TCP,其主要有以下作用:
-
幫助鏡像使用理解這個鏡像服務的守護端口,以方便配置映射。
-
在運行時使用隨時端口映射,也就是docker run -P時,會自動隨機映射EXPOSE的端口。
EXPOSE指令實際上并未發布指定端口,它是將我們訪問時輸入的端口和運行容器之間做一種關聯或通信,具體發布哪些端口,要在運行容器時發布實際端口,可以使用docker run中-p或-P來設置映射的端口號。
默認情況下一般如果我們未指定協議的話,那么都是使用TCP協議,當然也可以指定具體的協議,也可以同時指定TCP和UDP協議。???????
EXPOSE 80/tcp
EXPOSE 80/udp
6.ENV
設置環境變量,定義了環境變量,這樣在后續的指令中,就可以使用這個環境變量。如果在環境變量引用過程中包含空格,那么需要使用到反斜杠。
其語法格式如下:???????
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
使用ENV設置的環境變量會保留下來,當容器運行時可以使用docker inspect查看值,并且可以使用docker run --env = 更改環境變量的值。
如果僅僅是在構建過程中需要環境變量,而在最終映像中不需要,可以考慮為單個命令設置一個值或使用ARG,ARG指令不會人保留在最終鏡像中。???????
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y ...
7.ARG
ARG構建參數與ENV作用一致,不過作用域不一樣,ARG設置的環境變量僅對Dockerfile內有效,也就是說只有docker build的過程中有效,構建好的鏡像內不存在此環境變量。
構建命令docker build中可以用--build-arg <參數名>=<值>來覆蓋,ARG指令格式如下:
ARG <參數名>[=默認值]
如果ARG指令具有默認值,并且在構建時未傳遞任何值,則構建時會使用默認值,相反如果未設置默認值,并且在構建中未傳遞任何值,那么構建時會輸出警告信息。
8.ADD
ADD指令是指將宿主機的文件或目錄復制到鏡像文件系統中指定的路徑,其語法格式有兩種:???????
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
每個可以使用通配符,并且匹配將使用Go的filepath.Match規則進行,例如:要添加所有以“hom”開頭的文件:
ADD hom* /mydir/
“?”可以替換任意一個字符,例如:
ADD hom?.txt /mydir/
是指構建容器的路徑,可以是絕對路徑,也可以是相對路徑,但這個相對路徑是相對于WORKDIR的路徑。如以下示例是將“test.txt”文件添加到/relativeDir/。
ADD test.txt relativeDir/
下面的示例則是將“test.txt”添加到相對路徑/absoluteDie/中。
ADD test.txt /absoluteDir/
如果路徑中包含一些特字符,那么需要按照Golang規則轉義那些路徑,以防止在解析過程中將它們視為匹配的模式,如要添加名為sarr[1].txt的文件。
ADD sarr[[]1].txt /mydir/
路徑必須是構建內容的一個內部路徑,不能添加類似于ADD ../path /path,因為docker build的第一步是將上下文目錄和子目錄發送到docker守護進程。
如果是URL,而不以斜杠結尾,則從URL下載文件并將其復制到。
如果是URL,而以斜杠結尾,則從URL推斷文件名,并將文件下載到/,例如ADD http://chuanshi.com/foobar /,將創建文件/foobar,該URL必須具有正確的路徑,以便在這種情況下可以找到適當的文件名。
如果是目錄,則復制目錄的整個內容,包括文件系統元數據。
如果是任何其他類型的文件,則會將其及其元數據一起單獨復制,在這種情況下,如果以斜杠結尾,則它將被視為目錄,并且的內容將被寫在/base()中。
如果直接或由于使用通配符而指定多個資源,則必須是目錄,并且必須以斜杠結尾。
如果不以斜杠結尾,則將其視為常規文件,并且的內容將被寫入。
如果不存在,則會與路徑中所有缺少的目錄一起創建它。
9.COPY
復制指令,從上下文目錄中復制文件或者目錄到容器里指定路徑,通常有以下兩種格式:???????
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
COPY指令表示從復制新文件或目錄,并將它們添加到容器的文件系統中,路徑為。
可以指定多個資源,但是文件和目錄的路徑將被解釋為相對于構建上下文。
每個可能包含通配 符,并且匹配將合作Go人filepath.Match規則進行。
例如要添加所有"hom"開頭的文件:
COPY home* /mydir/
"?"表示可以匹配任意一個字符:
COPY hom?.txt /mydir/
是指構建容器的路徑,可以是絕對路徑,也可以是相對路徑,但這個相對路徑是相對于WORKDIR的路徑。如以下示例是將“test.txt”文件添加到/relativeDir/。
COPY test.txt relativeDir/
下面的示例則是將“test.txt”添加到相對路徑/absoluteDie/中
COPY test.txt /absoluteDir/
如果路徑中包含一些特字符,那么需要按照Golang規則轉義那些路徑,以防止在解析過程中將它們視為匹配的模式,如要添加名為sarr[1].txt的文件。
COPY sarr[[]1].txt /mydir/
路徑必須是構建內容的一個內部路徑,不能添加類似于COPY ../path /path,因為docker build的第一步是將上下文目錄和子目錄發送到docker守護進程。
如果是目錄,則復制目錄的整個內容,包括文件系統無數據,目錄本身不會被復制,只是其內容被復制。
如果是任何其他類型的文件,則會將其及其元數據一起單獨復制,在這種情況下,如果以斜杠結尾,則它將被視為目錄,并且的內容將被寫在/base()中。
如果直接或由于使用通配符而指定多個資源,則必須是目錄,并且必須以斜杠結尾。
如果不以斜杠結尾,則將其視為常規文件,并且的內容將被寫入。
如果不存在,則會與路徑中所有缺少的目錄一起創建它。
10.ENTRYPOINT
類似于CMD指令,但其不會被docker run指令運行參數所覆蓋,并且這些命令行參數會被當作參數送給ENTRYPOINT指令指定的程序。
但是如果運行docker run時使用了--entrypoint選項,將覆蓋CMD指令指定的程序。
其語法格式有以下兩種:???????
#exec格式
ENTRYPOINT ["executable", "param1", "param2"]
#shell格式
ENTRYPOINT command param1 param2
ENTRYPOINT的優點在執行docker run的時候可以指定ENTRYPOINT運行所需要的參數。
但如果Dockerfile中如果存在多個ENTRYPOINT指令時,只有最后一個會生效。
可以將ENTRYPOINT與CMD命令搭配使用,一般是變參才會使用CMD,這里的CMD等于是在給ENTRYPOINT傳參。
例如,通過Dockerfile構建了一個nginx:V1.0的鏡像。???????
FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定參
CMD ["/etc/nginx/nginx.conf"] # 變參
如果在運行容器時,不傳參運行
root@ubunhomhome/test# docker run -it nginx:v1.0
容器內則會默認運行以下命令,啟動主進程
nginx -c /etc/nginx/nginx.conf
如果在運行容器時傳參運行
root@ubunhomhome/test# docker run -it nginx:v1.0 -c /etc/nginx/new_nginx.conf
容器內會默認運行以下命令,啟動主進程
nginx -c /etc/nginxnew_nginx.conf
11.VOLUME
VOLUME指令在16.7小節會進行詳細的介紹,在本小節中不做介紹;
12.USER
USER指令用于指定執行后續命令的用戶和用戶組,這邊只是切換后續指令執行的用戶(用戶和用戶組必須提前已經存在)。其語法格式如下:???????
USER <user>[:<group>]
#or
USER <UID>[:<GID>]
13.WORKDIR
WORKDIR指令表示指定工作目錄,該指定的目錄必須提前創建好,docker build構建鏡像過程中的,每個RUN命令都是新建的一層,只有通過WORKDIR創建才會一直存在。其語法格式如下:
WORKDIR /path/to/workdir
WORKDIR指令可在Dockerfile中多次使用,如果提供了相對路徑,則它將相對于上一個WORKDIR指令的路徑來創建目錄,如:???????
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
該Dockerfile中最后一個pwd命令的輸出為/a/b/c。
WORKDIR指令也可以解析ENV設置的環境變量,如:???????
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
該Dockerfile中最后一個pwd命令的輸出為/path/$DIRNAME。
?