Like Share Discussion Bookmark Smile

J.J. Huang   2019-03-25   Docker   瀏覽次數:次   DMCA.com Protection Status

Docker - 第三章 | Dockerfile 指令教學

在前面介紹Docker的文章中,我們都是從Docer Hub中下載映像檔(Image)來建立Container ,這些映像檔可能是軟體開發商所釋出,或者是第三方人士加值過的版本,這種做法的好處是很方便,但缺點則是映像檔包含的功能、工具或版本無法滿足自己的需求,此時利用Dockerfile 客製化一個符合需求的映像檔,就是一個很好的解決方案。

在開始之前建議先將前面兩章節閱讀完畢第一章- Docker 簡介 第二章- Docker 基本指令,建立一些基礎概念。

什麼是Dockerfile ?

  • 是一個文字檔,由一行一行的指令所組成,用來描述這個映像檔應該長成怎麼樣
  • 利用Dockerfile可以建構/客製化出自己獨一無二的映像檔
  • 由於Dockerfile中可以清楚的知道映像檔的組成,因此,在安全性上會有所提升
  • 因為是純文字檔,所以檔案很小、很容易分享

Dockerfile 的組成

基本上Dockerfile是由一行一行的指令列所組成,一行指令對Image來說就是一層的資料層(Layer),一個Image就是靠這樣一層一層的資料累加上去,最後才編譯出自己想要的映像檔,就像蓋房子一樣。


Dockerfile 指令介紹

COMMENT (#註解)

1
2
檔案中可使用 # 符號來進行註解。
# 我是註解唷,在這符號之後的都不會被執行

FROM (基底)

基底映像檔,必需是「第一個」指令行,指定這個映像檔要以哪一個Image為基底來建構,格式為FROM <image> 或 FROM <image>:<tag>

1
2
FROM ubuntu:15.04
FROM ubuntu

MAINTAINER (維護者)

映像檔維護者,把它想成是作者即可,格式為MAINTAINER <name>

1
2
3
MAINTAINER JJ.Huang 或
MAINTAINER JJ@myemail.com 或
MAINTAINER JJ.Huang@myemail.com

LABEL (標籤)

設定映像檔的Metadata資訊,例如:作者、EMail、映像檔的說明等
LABEL <key>=<value> <key>=<value> <key>=<value> …

1
2
3
4
5
6
7
LABEL description="LABEL範例" version="1.0" owner="J.J.Huang"

# 或是

LABEL description="LABEL範例"
LABEL version="1.0"
LABEL owner="J.J.Huang"

註:可以利用docker inspect命令進行查看。

RUN (執行)

執行指定的指令,每加一個RUN,就會在基底映像層加上一層資料層,以此類推,一層一層的建構起我們最後想要的映像檔,例如我們可以利用RUN來安裝套件,其格式分為二種

1
2
3
4
5
RUN <command>

# 或是

RUN ["executable", "param1", "param2"]

第一種後邊直接跟shell命令:

  • 在linux操作系統上預設 /bin/sh -c
  • 在windows操作系統上預設 cmd /S /C

第二種是類似於函數調用:

  • 將executable理解成為可執行檔案,後面就是兩個參數。

__Shell形式與exec形式有什麼不同呢? __

1
2
3
4
5
6
7
Unlike the shell form, the exec form does not invoke a command shell.
This means that normal shell processing does not happen. For example,
RUN [ "echo", "$HOME" ] will not do variable substitution on $HOME

意思是說exec執行的方式不會使用command shell,所以執行 RUN [ "echo", "$HOME" ]
這樣的指令列, $HOME 這個變數是不會被替代(填入值)的,也就是直接輸出「$HOME」,
但如果你想要有Shell處理的功能,則可以自行指定shell來達成:RUN [ "sh", "-c", "echo $HOME" ]

在使用RUN指令時,注意事項:

  • 如果想要執行的指令很長,可以利用 \ 符號來換行,比較容易閱讀
  • 使用exec形式執行時,必需使用JSON array的格式,因此,請使用雙引號
  • 每一個RUN就會新增一層資料層,為了減少不必要的資料層,可以利用 && 來串連多個命令

簡單的RUN指令範例

1
2
3
4
5
6
7
8
9
10
11
# install packages, only for demo
RUN mkdir -p /home/demo/docker
RUN ["apt-get", "install", "python3"]
RUN apt-get update && apt-get install -y --force-yes apache2 \
    firefox \
    php5s

# 解釋上方RUN指令動作
# 建立資料夾/home/demo/docker
# 使用apt-get安裝python3
# 更新apt-get 然後 使用apt-get安裝apache2和firefox和php5s

CMD (啟動時命令)

功能為容器啟動時要運行的命令, 語法有三種寫法

1
2
3
4
5
6
7
8
# exec形式,官方推薦此種方式
1. CMD ["executable","param1","param2"]

# 適用於有定義ENTRYPOINT指令的時候,CMD中的參數會做為ENTRYPOINT的預設參數
2. CMD ["param1","param2"]

# 會以shell的形式執行,預設是在「/bin/sh -c」下執行,適合在需要互動的指令時
3. CMD command param1 param2

使用CMD的注意事項:

  • Dockerfile中只能有一行CMD,若有多行CMD,則只有最後一行會生效
  • 若在建立Container時有帶執行的命令,則CMD的指令會被蓋掉,例如:執行docker run 時,CMD所定義的指令會被執行,但當執行docker run bash時,Container就會執行bash,而原本CMD中定義的值就會覆蓋

簡單的CMD指令範例

1
2
3
4
5
CMD echo "This is a test." | wc -
CMD ["/usr/bin/wc","--help"]
CMD [ "sh", "-c", "echo $HOME" ]
#作為ENTRYPOINT的參數使用
CMD ["Hello"]

ENTRYPOINT (啟動預設命令)

和CMD一樣,用來設定映像檔啟動Container時要執行的指令,但不同的是,ENTRYPOINT一定會被執行,而不會有像CMD覆蓋的情況發生,語法有兩種寫法

1
2
3
4
5
# exec形式,官方推薦此種方式
1. ENTRYPOINT ["executable", "param1", "param2"]

# shell的形式
2. ENTRYPOINT command param1 param2

使用ENTRYPOINT,注意事項:

  • Dockerfile中只能有一行ENTRYPOINT,若有多行ENTRYPOINT,則只有最後一行會生效
  • 若在建立Container時有帶執行的命令,ENTRYPOINT的指令不會被覆蓋,也就是一定會執行
  • 如果想要覆蓋ENTRYPOINT的預設值,則在啟動Container時,可以加上「–entrypoint」的參數,例如:docker run –entrypoint

舉個實例,來看看ENTRYPOINT與CMD的關係,假設Dockerfile中的定義如下:

1
2
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["World"]

如果是使用docker run -it <image>來啟動Container,那麼輸出的結果為「Hello World」,但如果是用docker run -it <image> Docker來啟動,則輸出結果會變成「Hello Docker」,因為CMD的值被覆蓋掉了

COPY (複製檔案)

複製本地端的檔案/目錄到映像檔的指定位置中

1
2
COPY <來源路徑>... <目標路徑>
COPY ["<來源路徑1>",... "<目標路徑>"]

使用COPY的注意事項:

  • 指令的來源位置可以多個
  • 如果目的位置是目錄的話,記得最後要以/結尾,例如:/mypath/
  • 目的位置可以是絕對路徑或者相對於WORKDIR定義值的相對路徑
  • 若目的位置不存在,會自動建立

COPY的範例

1
2
COPY file1.txt file2.js file3.json ./
COPY ["file1.txt", "file2.js", "file3.json" "./"]

ADD (高級複製檔案)

ADD 指令和 COPY 的格式和性質基本一致。但是在 COPY 基礎上增加了一些功能。比如<來源路徑>可以是一個 URL,這種情況下,Docker 會試圖去下載這個URL的檔案放到<目標路徑>去

使用ADD的注意事項:

  • ADD的來源路徑支援URL,也就是說可以加入遠端的檔案,COPY則不支援URL
  • 若來源檔案是壓縮檔(副檔名為gzip、bzip2、xz),則使用ADD加入檔案時會自動解壓縮,而COPY不會

ADD的範例

1
2
3
ADD file1.txt file2.js file3.json ./
# ENV_DEMO_VALUE 是用ENV指令所設定的環境變數
ADD https://www.google.com/demo.gzip $ENV_DEMO_VALUE

除非你有自動解壓的需求,不然一般建議會使用「COPY」來加入檔案

EXPOSE (設置埠)

宣告在映像檔中預設要使用(對外)的連接埠

1
EXPOSE <port> [<port>/<protocol>...]

EXPOSE範例

1
2
3
# EXPOSE預設的協定是TCP,但如果不是要TCP的話
EXPOSE 80/tcp
EXPOSE 80/udp

EXPOSE注意事項:

  • 使用EXPOSE所定義的連接埠並不會自動的啟用,而只是做提示的作用而已,要將連接埠啟用需要在執行docker run時,搭配-p或-P的參數來啟用。
  • 小寫的-p可以自行指定與主機關聯的連接埠
  • 大寫的-P則會啟用所有EXPOSE所定義的連接埠,並動態(隨機)的關聯到主機的連接埠,例如:EXPOSE 80 可能隨機關聯到主機的 45123 連接埠
    1
    2
    3
    4
    # 小寫的p
    docker run -p 80:80/tcp -p 80:80/udp demo
    # 大寫的P
    docker run -P demo

ENV (環境變數)

使用ENV設置環境變數後,在Dockerfile中其他的指令就可以利用,之後在建起來的Container裡也可以使用該變數

1
2
ENV <key> <value>
ENV <key>=<value> ...

ENV範例

1
2
3
4
5
6
7
ENV demoPATH="/var/log" demoVer="1.0"
# 設置「/tmp/test.txt」給demoFile變數
ENV demoFile /tmp/test.txt

# 使用環境變數的例子,有沒有用大括號都可以
COPY debug.log ${demoPATH}
ADD $demoFile /foo

VOLUME (其他容器的掛載點)

1
2
3
4
VOLUME ["/var/log/"] 或
VOLUME ["/demo1","/demo2"] 或
VOLUME /var/log
VOLUME /var/log /var/db

一個 Volume 可以存在於一個或多個容器的指定目錄,該目錄可以繞過聯合文件系統,並具有以下功能:

  • Volume 可以 Container 間共享和重用
  • Container 並不一定要和其它 Container 共享 Volume
  • 修改 Volume 後會立即生效
  • 對 Volume 的修改不會對 image 產生影響
  • Volume 會一直存在,直到沒有任何 Container 在使用它

Volume 讓我們可以將源代碼、資料或其它內容添加到 image 中,而又不並提交到 image 中,並使我們可以多個 Container 間共享這些內容。

VOLUME注意事項:

  • 要特別注意的是使用 Volume 來定義掛載點時,是無法指定本機對應的目錄的,對應到哪個目錄是自動產生,我們可以透過docker inspect來查詢目錄資訊

WORKDIR (設定工作目錄)

當設定WORKDIR後,Dockerfile中的RUN、CMD、ENTRYPOINT、COPY、ADD等指令就會在該工作目錄下執行

1
2
3
4
5
6
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

#pwd最後會在/a/b/c目錄下執行,如果目錄不存在,系統會幫忙自動建立

USER (指定當前用戶)

指定運行Container時的用戶名稱或UID

1
2
USER <user>[:<group>]
USER <UID>[:<gid>]

USER範例

1
2
3
4
5
6
RUN groupadd -r tester && useradd -r -g tester tester
# 指定用戶名稱
USER tester

# 或使用UID來指定
USER 1000

USER注意事項:

  • 在定義了USER後,則Dockerfile中的RUN、CMD、ENTRYPOINT等指令便會以USER指定的用戶來執行,前提條件是該用戶必需是已存在的,否則指定會失敗

ARG (設置構建參數)

設定在建置映像檔時可傳入的參數,即定義變數名稱以及變數的預設值

1
ARG <name>[=<default value>]

ARG範例

1
2
ARG Param1
ARG Param2=somevalue

建構映像檔案,可利用–build-arg =來指定參數

1
2
3
docker build --build-arg Param1=demo -t myimage:v1 .

#在上面的例子中,我們在docker build中利用–build-arg <varname>=<value>參數將Param1的值變更為「demo」,而Param2的值並沒有指定,所以保留預設值「somevalue」

ARG注意事項:

  • ARG和ENV的功能類似,都可以設定變數,但是ARG設定的值是供建置映像檔時使用(搭配docker build指令),在Container中是無法使用這些變數的,相反地,ENV的值則可以在Container中存取

ONBUILD

若這個映像檔是作為其他映像檔的基底時,便需要定義ONBUILD指令

1
ONBUILD [INSTRUCTION]

ONBUILD範例
A映像檔的Dockerfile定義如下(假設名稱為A-Image)

1
2
3
4
...(以上略)
ONBUILD ADD . /home/tmp
ONBUILD mkdir -p /home/demo/docker
...(以下略)

如果B映像檔是以A映像檔為基底,則A映像檔中的ONBUILD指令就會被觸發

1
2
3
4
5
6
7
# 以A映像檔為基底
FROM A-Image

# 觸發A映像檔ONBUILD的指令,即會自動執行下面二個指令行
# 下面二行指令不用自己加,Docker會自動去執行,這邊寫出只是方便做說明
ADD . /home/tmp
mkdir -p /home/demo/docker

ONBUILD注意事項:

  • ONBUILD後面接的指令在自建的映像檔中不會被執行,只有當這個映像檔是作為其他映像檔的基底時才會被觸發

如何使用Dockerfile ?

利用Dockerfile來建立我們自己的映像檔,其指令為docker build

1
2
# 在目前目錄尋找Dockerfile或dockerfile
docker build -t myimage:v1 .

-t:Name and optionally a tag in the ‘name:tag’ format,指定映像檔名稱、標籤

假設Dockerfile在當前目錄下,因此會以.結尾,若是在不同目錄,則可以直接接Dockerfile所在目錄或用-f來指定Dockerfile位置

1
2
3
# 後面接Dockerfile的所在目錄
docker build -t myimage:v2 ./docker
docker build -f /path/to/a/Dockerfile -t myimage:v3 .

Docker Build注意事項:

  • -f 來指定Dockerfile的位置時,後面接的目錄(及其子目錄)需要能夠找到Dockerfile,否則會出現context錯誤
  • 有時,想要建立沒有快取的映像。可能正在調試建置問題並希望從頭開始。或者可能想要強制升級依賴項。無論出於何種原因,可以使用該選項建立沒有快取的圖像 --no-cache
    1
    docker build --no-cache -t myimage:v1 .
  • 在建立的時候,輸出的內容可能被精簡了,希望輸出完整的過程內容,可以使用 --progress=plain 顯示容器輸出。
    1
    docker build --progress=plain -t myimage:v1 .

    註:其他的指令可以參考docker buildx build


註:以上參考了
twtrubiksDocker 基本教學 - 從無到有 Docker-Beginners-Guide文章。
oKongDocker | 第四章:Dockerfile简单介绍及使用文章。
靖.技場Docker – Dockerfile 指令教學,含範例解說文章。
docker buildx build