Dockerfile

本文章內容:

1.Dockerfile指令

2.使用Dockerfile文件創建鏡像

 

1. Dockerfile指令

man dockerfile可以查看幫助。

1.1  FORM

FROM是最重要的一個指令,且必須為Dockerfile文件的第一個非注釋行指令。用於為鏡像文件構建過程指定基準鏡像,後續的指令運行於此基準鏡像所提供的運行環境。

Syntax:

  • FROM <repository>:<tag>
  • FROM <repository>@<digest>
    說明:
    <repository>:鏡像的倉庫。
    <tag>:鏡像的標籤,省略則為latest。
    <digest>:比對hash碼,防止鏡像被竄改。

1.2  MAINTAINER (將捨棄,可以使用LABEL指令取代)

用於讓鏡像製作者提供本人的詳細資訊。Dockerfile並不限制MAINTAINER指令出現的位置,但建議將其放置於FROM指令之後。

Syntax:

  • MAINTAINER "作者的詳細資訊"
    說明:"作者的詳細資訊"可以是任何文本訊息,但約定俗成大都填寫作者及郵件地址,例如:MAINTAINER "tom <tom@gmail.com>"。

1.3  LABEL

為鏡像指定各種元數據,元數據都是鍵值對格式的。

Syntax:

  • LABEL <key>=<value> [<key>=<value> ...]
  • LABEL <key> <value>
    LABEL <key> <value>
    ...
    例如LABEL maintainer="tom <tom@gmail.com>"就可以替代MAINTAINER "tom <tom@gmail.com>"。

1.4  COPY

用於從Docker主機複製文件到創建的新映像文件。

Syntax:

  • COPY <src> ... <dest>
  • COPY ["<src>",... "<dest>"]:路徑中有空白字符時使用這種格式
    說明:
    <src>:要複製的源文件或目錄,支持使用通配符。
    <dest>:目標路徑,即正在創建的image的文件系統路徑,建議使用絕對路徑,否則COPY指定以WORKDIR為其起始路徑。

操作準則:

  • <src>:必須是build上下文中的路徑,不能是其父目錄中的文件。
  • 如果<src>是目錄,則其內部文件或子目錄會被遞歸複製,但<src>目錄自身不會被複製。
  • 如果指定了多個<src>或在<src>中使用了通配符,則<dest>必須是一個目錄,且必須以"/"結尾。
  • 如果<dest>事先不存在將會被自動創建,包括其父目錄路徑。

1.5  ADD

同COPY指令,但ADD指令支持使用tar文件和url路徑。

Syntax:

  • ADD <src> ... <dest>
  • ADD ["<src>",... "<dest>"]

操作準則:

  • 如果<src>為url且<dest>不以"/"結尾,則<src>指定的文件將被下載並創建為<dest>;如果<dest>以"/"結尾,<src>指定的文件將被下載並保存為<dest>/<src>指定的文件。
  • 如果<src>是一個本地系統上(或壓縮或不壓縮)的tar文件,它將被自動展開,其行為類似於"tar -xf"命令,但是通過url或取到的tar文件將不會自動展開。
  • 如果<src>有多個,或間接或直接使用了通配符,則<dest>必須是一個以"/"結尾的目錄路徑;如果<dest>不以"/"結尾,則會被視作一個普通文件,<src>的內容將被直接寫入到<dest>。

1.6  WORKDIR

用於為Dockerfile文件中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指令指定工作目錄。

Syntax:

  • WORKDIR /path/to/workdir

操作準則:

  • 在Dockerfile文件中WORKDIR指令可以出現多次,其路徑也可以為相對路徑但是其是相對此前一個WORKDIR指令指定的路徑。
  • WORKDIR指令可以調用由ENV指令定義的變量。

補充說明:docker container run加-it選項到交互模式後,其當前目錄就是WORKDIR。

1.6.1  示例1

[root@localhost img1]# vim Dockerfile
# Discription: test image
FROM busybox:latest
WORKDIR /data/web/html/  #指定工作目錄
COPY index.html ./  #index.html會複製到/data/web/html/
WORKDIR /usr/local/src/  #指定工作目錄
ADD nginx-1.19.7.tar.gz ./  # nginx-1.19.7會複製到/usr/local/src/

1.7  VOLUME

用於在鏡像中創建一個掛載點目錄,以掛載Docker Host上的卷或其他容器上的卷。

Syntax:

  • VOLUME ["掛載點"]

操作準則:

  • 使用的是Docker管理卷方式而不是綁定掛載卷方式。
  • 如果鏡像掛載點存在有文件,那創建容器時會將掛載點內的文件複製到卷上。

1.8  EXPOSE

用於為容器打開指定要監聽的端口,以實現與外部通訊。

Syntax:

  • EXPOSE <port>[/<protocol>] [<port>[/<protocol>] ...]
    說明:<protocol>:默認為tcp協議。

操作準則:

  • 僅能指定自己暴露什麼端口,而後會動態綁定至宿主機的所有地址和隨機端口,因為你不知道將來創建的容器會在哪台宿主機上以及宿主機上有哪些空閒的端口。
  • 此指令僅說明自己要暴露什麼端口但並不會真正暴露,要真正暴露需在創建容器時搭配"-P"選項,"-P"選項就是暴露鏡像EXPOSE指定的端口,如果鏡像沒設定EXPOSE,則"-P"選項不會暴露任何端口。

1.9  ENV

用於為鏡像定義所需的環境變量,並可被Dockerfile文件中位於其後的其他指令(如ENV、ADD、COPY等)所調用。

Syntax:

  • ENV <key> <value>
  • ENV <key>=<value> [<key>=<value> ...]
    說明:調用格式為$variable_name或${variable_name}。

操作準則:

  • 第一種語法格式中,<key>之後的所有內容均會被視作<value>的組成部分不管有沒有空格,因此一次只能設定一個變量。
  • 第二種語法格式中,可以一次設定多個變量,每個變量為一個<key>=<value>的鍵值對
  • 如果<value>中包含空格,則需使用反斜線"\"進行轉義,也可以通過對<value>加引號進行標識,另外,反斜線"\"也可以用於續航。
  • 設定多個變量時建議使用第二種語法格式,以便在同一層中完成所有功能,例如:
    ENV <key>=<value> \
    <key>=<value> \
    ...

補充說明:

docker build後的image其ENV是dockerfile中寫的ENV;docker run時加上-e選項可以修改ENV的值。

1.10  RUN

用於指定docker image build過程中運行的程序,可以是任何命令,只要基礎鏡像中有的話。

Syntax:

  • RUN <command>
  • RUN ["<executable>", "<param1>", "<param2>"]

操作準則:

  • 第一種語法格式中,<command>通常是一個shell命令,且以"/bin/sh -c"來運行它,這意謂著此進程在容器中的PID不為1,不能接收Unix信號,因此當使用docker stop <container>命令停止容器時,此進程接收不到SIGTERM信號。(測試還是可以使用docker container stop <container>命令來停止容器)
  • 第二種語法格式中的參數是一個JSON格式的數組,其中<executable>為要運行的指令,後面的<paramN>為傳遞給指令的選項或參數,然而此種語法格式指定的指令不會以"/bin/sh -c"來發起(是以內核來發起),因此常見的shell操作如變量替換以及通配符替換將不會進行,不過如果要運行的指令依賴於此shell的特性的話,則可以將其替換為類似下面的語法格式:
    RUN ["/bin/sh", "-c", "<param>"] (param是一個完整的指令加參數,當作/bin/sh的子進程來啟動)
    注意:在JSON格式的數組中,要使用雙引號。
  • 要運行的相關聯的程序可以使用"&&"結合,盡量在同一層中完成所有功能,例如:
    RUN <command> && \
    <command> && \
    ...
    or
    RUN ["/bin/sh", "-c", "<param1> && \
             <param2>"] (param是一個完整的指令加參數,當作/bin/sh的子進程來啟動)

1.11  CMD

用於指定docker container [creat|run]過程中默認運行的程序,但會被docker container [create|run]命令傳入的命令參數所覆蓋

Syntax:

  • the command is run in a shell - /bin/sh -c:
    CMD command param1 param2
  • Executable form:
    CMD ["executable", "param1", "param2"]
  • Provide default arguments to ENTRYPOINT:
    CMD ["param1", "param2"]

操作準則:

  • 前兩種語法格式的意義同RUN指令。
    • 第一種語法格式中,<command>通常是一個shell命令,且以"/bin/sh -c"來運行它,這意謂著此進程在容器中的PID不為1,不能接收Unix信號,因此當使用docker stop <container>命令停止容器時,此進程接收不到SIGTERM信號。(測試還是可以使用docker container stop <container>命令來停止容器)
    • 第二種語法格式中的參數是一個JSON格式的數組,其中<executable>為要運行的指令,後面的<paramN>為傳遞給指令的選項或參數,然而此種語法格式指定的指令不會以"/bin/sh -c"來發起(是以內核來發起),因此常見的shell操作如變量替換以及通配符替換將不會進行,不過如果要運行的指令依賴於此shell的特性的話,則可以將其替換為類似下面的語法格式:
      RUN ["/bin/sh", "-c", "<param>"] (param是一個完整的指令加參數,當作/bin/sh的子進程來啟動)
      注意:在JSON格式的數組中,要使用雙引號。
      補充說明:RUN ["/bin/sh", "-c", "httpd", "-f", "-h", "/data/web/html"]寫這樣容器啟動就會關閉,RUN ["/bin/sh", "-c", "httpd -f -h /data/web/html"]寫這樣容器啟動不會關閉,因為這樣就像CMD command param1 param2語法一樣,使用shell來運行command param1 param2。注意:"/bin/sh", "-c"不能寫成"/bin/sh -c",因為沒有/bin/sh -c這個指令,只有/bin/sh這個指令。
      測試方式:
      a. 執行docker container run --name t1 --rm busybox /bin/sh -c /bin/httpd -f -h /data/web/html,這樣容器啟動就會關閉,因為-c是/bin/sh的選項而"/bin/httpd"、"-f"、"-h"、"/data/web/html"都是/bin/sh的參數
      b. 執行docker container run --name t1 --rm busybox /bin/sh -c '/bin/httpd -f -h /data/web/html',這樣容器啟動不會關閉,因為-c是/bin/sh的選項而"/bin/httpd -f -h /data/web/html"是/bin/sh的參數
      c. 在宿主機安裝httpd後,執行/bin/sh -c httpd -D FOREGROUND不會跑在前台,但執行/bin/sh -c 'httpd -D FOREGROUND'就會跑在前台
  • 第三種語法格式則用於為ENTRYPOINT指令提供默認參數(當CMD與ENTRYPOINT指令一起使用時)。注意:ENTRYPOINT指令執行的命令必須完整,CMD指令提供的參數只是額外的。
    例如:
    a. ENTRYPOINT ["/bin/sh","-c","/bin/httpd -f -h /data/web/html"]的"/bin/httpd -f -h /data/web/html"必須是完整可以啟動服務的命令,如果是ENTRYPOINT ["/bin/sh","-c","/bin/httpd -f -h"]而/data/web/html是靠CMD ["/data/web/html"]來提供參數則不行。
    b. ENTRYPOINT ["/bin/sh","-c"] + CMD ["/bin/httpd -f -h /data/web/html"]這樣也可以。這樣就可以把/bin/sh -c當父進程來靈活的啟動任何子進程,啟動完子進程後父進程退位,子進程PID變為1。
  • 可以寫多個CMD指令,但僅最後一個有效。

1.12  ENTRYPOINT

與CMD指令一樣用於指定docker container [creat|run]過程中默認運行的程序,但不會被docker container [create|run]命令傳入的命令參數所覆蓋,並且傳入的命令參數會被當作參數傳遞給ENTRYPOINT指令指定的程序。

補充說明:不過,docker container [create|run]命令的"--entrypoint"選項的參數可以覆蓋ENTRYPOINT指令指定的程序。例:docker container run --name test --rm --entrypoint ls custom:v0.1 /,--entrypoint選項後面接上要運行的命令,如果要運行的命令有參數的話加在最後面。

Syntax:

  • run command in a shell - /bin/sh -c:
    ENTRYPOINT command param1 param2
  • executable form:
    ENTRYPOINT ["executable", "param1", "param2"]

操作準則:

  • docker container [create|run]命令傳入的命令參數會覆蓋CMD指令的內容並且附加到ENTRYPOINT指令最後,作為其參數使用。
  • 可以寫多個ENTRYPOINT指令,但僅最後一個有效。
  • 使用CMD指令也使用ENTRYPOINT指令,則CMD指令會當作參數傳遞給ENTRYPOINT指令,但如果docker container run時,最後面加上要執行的指令,則該指令會覆蓋CMD成為ENTRYPOINT的參數。

範例:

正確1:
CMD ["-f","-h","/data/web/html/"]  #當ENTRYPOINT的選項及參數。"-f"、"-h"是httpd指令的選項,"/data/web/html/"是"httpd"指令的參數
ENTRYPOINT ["httpd"]

正確2:
CMD ["/data/web/html/"]  #當ENTRYPOINT的參數。"/data/web/html/"是"httpd"指令的參數
ENTRYPOINT ["httpd","-f","-h"]  #"-f"、"-h"是"httpd"指令的選項

正確3:
CMD ["httpd -f -h /data/web/html/"]  #當ENTRYPOINT的參數。"httpd -f -h /data/web/html/"是"/bin/sh"指令的參數
ENTRYPOINT ["/bin/sh","-c"]  #"-c"是"/bin/sh"指令的選項

錯誤1:
CMD ["-f","-h","/data/web/html/"]  #當ENTRYPOINT的參數。"-f"、"-h"是"/bin/sh"指令的選項,"/data/web/html/"是"/bin/sh"指令的參數
ENTRYPOINT ["/bin/sh","-c","httpd"]  #"-c"、"httpd"是"/bin/sh"指令的選項,但"/bin/sh"指令除了認識"-c"選項其他都不認識

錯誤2:
CMD ["/data/web/html/"]  #當ENTRYPOINT的參數。"/data/web/html/"是"/bin/sh"指令的參數
ENTRYPOINT ["/bin/sh","-c","httpd","-f","-h"]  #"-c"、"httpd"、"-f"、"-h"是"/bin/sh"指令的選項,但"/bin/sh"指令除了認識"-c"選項其他都不認識

1.13  USER

用於指定運行image時的或運行Dockerfile中任何RUN、CMD或ENTRYPOINT指令指定的程序時的用戶名或UID。默認情況下,container的運行身分為root用戶。

Syntax:

  • USER [user|user:group|uid|uid:gid|user:gid|uid:group]
    注意:確保容器內的/etc/passwd文件中有指定USER的用戶,但root用戶不受影響,因為root用戶是內核的,就算容器內沒有/etc/passwd文件也不受影響。

1.14  HEALTHCHECK

參考資料:

https://docs.docker.com/engine/reference/builder/#healthcheck

https://yeasy.gitbook.io/docker_practice/image/dockerfile/healthcheck

https://beginor.github.io/2018/03/11/healthy-check-instruction-of-docker.html

1.15  SHELL

參考資料:

https://docs.docker.com/engine/reference/builder/#shell

https://yeasy.gitbook.io/docker_practice/image/dockerfile/shell

The default shell on Linux is ["/bin/sh","-c"],and on Windows is ["cmd","/S","/C"].

1.16  STOPSIGNAL

參考資料:

https://docs.docker.com/engine/reference/builder/#stopsignal

https://www.cnblogs.com/jiangbo44/p/14117358.html

1.17  ARG

和ENV指令的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。

補充說明:docker build時加上--build-arg選項可以修改ARG的值。

參考資料:

https://docs.docker.com/engine/reference/builder/#arg

https://yeasy.gitbook.io/docker_practice/image/dockerfile/arg

1.18  ONBUILD

用於在Dockerfile文件中定義一個觸發器,意思是從Dockerfile創建出鏡像時不會執行ONBUILD指令,但創建出的鏡像再被別人當作FROM的基礎鏡像時就會執行ONBUILD指令。

Syntax:

  • ONBUILD [INSTRUCTION]  #INSTRUCTION指Dockerfile的指令(不能使用FROM、MAINTAINER指令),例如ADD、COPY、RUN、CMD、...等等

2. 使用Dockerfile文件鏡像創建

使用Dockerfile文件製作鏡像須注意事項:

  • 在製作鏡像的目錄內,Dockerfile文件的首字母需大寫。
  • Dockerfile文件內容的第一行指令(不包含注釋)必須是FROM。
  • .dockerignore文件內容每行可指明忽略的文件(可使用通配符)。

2.1  示例1

1. 創建製作鏡像的位置,執行mkdir -p image並cd到裡面

[root@localhost ~]# mkdir -p image
[root@localhost ~]# cd image/

2. 執行vim Dockerfile,填寫以下內容

[root@localhost image]# vim Dockerfile
FROM nginx:stable-alpine  #以nginx-stable-alpine為基礎鏡像
LABEL maintainer="tom <tom@gmail.com>"

ENV NGX_DOC_ROOT='/data/web/html/'  #設定網站根目錄的環境變量

ADD entrypoint.sh /bin/  #複製entrypoint.sh腳本到/bin目錄下

CMD ["/usr/sbin/nginx","-g","daemon off;"]  #運行nginx服務於前台。這裡CMD和ENTRYPOINT指令一起使用,所以CMD指令是作為ENTRYPOINT指令的參數
ENTRYPOINT ["/bin/entrypoint.sh"]  #運行腳本

3. 執行vim entrypoint.sh,填寫以下內容並給予執行權限

[root@localhost image]# vim entrypoint.sh
#!/bin/sh
#
cat > /etc/nginx/conf.d/www.conf << EOF
server {
        server_name ${HOSTNAME};
        listen ${IP:-0.0.0.0}:${PORT:-80};
        root ${NGX_DOC_ROOT:-/usr/share/nginx/html};
}
EOF

exec "$@"  #執行腳本的參數,也就是CMD,而exec就是把執行"$@"頂替執行ENTRYPOINT成為main process
[root@localhost image]# chmod +x entrypoint.sh

4. 創建鏡像,執行docker image build -t myweb:v0.1 ./

[root@localhost image]# docker image build -t myweb:v0.1 ./
Sending build context to Docker daemon  3.072kB
Step 1/6 : FROM nginx:stable-alpine
 ---> b51b391571c1
Step 2/6 : LABEL maintainer="tom <tom@gmail.com>"
 ---> Using cache
 ---> 8735adb6ea3d
Step 3/6 : ENV NGX_DOC_ROOT='/data/web/html/'
 ---> Using cache
 ---> e47f14094224
Step 4/6 : ADD entrypoint.sh /bin/
 ---> Using cache
 ---> 9da30b586454
Step 5/6 : CMD ["/usr/sbin/nginx","-g","daemon off;"]
 ---> Using cache
 ---> e8f1ba519938
Step 6/6 : ENTRYPOINT ["/bin/entrypoint.sh"]
 ---> Using cache
 ---> 9f4a5030c364
Successfully built 9f4a5030c364
Successfully tagged myweb:v0.1
[root@localhost image]# docker image ls
REPOSITORY   TAG             IMAGE ID       CREATED        SIZE
myweb       v0.1            9f4a5030c364   2 hours ago    21.9MB

5. 創建容器並運行,執行docker container run --name myweb1 --rm myweb:v0.1

[root@localhost image]# docker container run --name myweb1 --rm myweb:v0.1

6. 執行docker container ls --no-trunc,看到容器運行的命令是"/bin/entrypoint.sh /usr/sbin/nginx -g 'daemon off;'"

[root@localhost image]# docker container ls --no-trunc
CONTAINER ID                                                       IMAGE                 COMMAND                                                 CREATED          STATUS          PORTS                   NAMES
d54a0ce7457990219eeca193d05d80280f00175f45ddb9b09ce10c12af7db111   myweb:v0.1           "/bin/entrypoint.sh /usr/sbin/nginx -g 'daemon off;'"   51 seconds ago   Up 49 seconds   80/tcp                  myweb1