Docker - 第三章 | Dockerfile 指令教學
在前面介紹Docker的文章中,我們都是從Docer Hub中下載映像檔(Image)來建立Container ,這些映像檔可能是軟體開發商所釋出,或者是第三方人士加值過的版本,這種做法的好處是很方便,但缺點則是映像檔包含的功能、工具或版本無法滿足自己的需求,此時利用Dockerfile 客製化一個符合需求的映像檔,就是一個很好的解決方案。
在開始之前建議先將前面兩章節閱讀完畢第一章- Docker 簡介 第二章- Docker 基本指令,建立一些基礎概念。
什麼是Dockerfile ?
- 是一個文字檔,由一行一行的指令所組成,用來描述這個映像檔應該長成怎麼樣
- 利用Dockerfile可以建構/客製化出自己獨一無二的映像檔
- 由於Dockerfile中可以清楚的知道映像檔的組成,因此,在安全性上會有所提升
- 因為是純文字檔,所以檔案很小、很容易分享
Dockerfile 的組成
基本上Dockerfile是由一行一行的指令列所組成,一行指令對Image來說就是一層的資料層(Layer),一個Image就是靠這樣一層一層的資料累加上去,最後才編譯出自己想要的映像檔,就像蓋房子一樣。
Dockerfile 指令介紹
COMMENT (#註解)
1 | 檔案中可使用 # 符號來進行註解。 |
FROM (基底)
基底映像檔,必需是「第一個」指令行,指定這個映像檔要以哪一個Image為基底來建構,格式為FROM <image> 或 FROM <image>:<tag>
1 | FROM ubuntu:15.04 或 |
MAINTAINER (維護者)
映像檔維護者,把它想成是作者即可,格式為MAINTAINER <name>
1 | MAINTAINER JJ.Huang 或 |
LABEL (標籤)
設定映像檔的Metadata資訊,例如:作者、EMail、映像檔的說明等
LABEL <key>=<value> <key>=<value> <key>=<value> …
1 | LABEL description="LABEL範例" version="1.0" owner="J.J.Huang" |
註:可以利用docker inspect命令進行查看。
RUN (執行)
執行指定的指令,每加一個RUN,就會在基底映像層加上一層資料層,以此類推,一層一層的建構起我們最後想要的映像檔,例如我們可以利用RUN來安裝套件,其格式分為二種
1 | RUN <command> |
第一種後邊直接跟shell命令:
- 在linux操作系統上預設 /bin/sh -c
- 在windows操作系統上預設 cmd /S /C
第二種是類似於函數調用:
- 將executable理解成為可執行檔案,後面就是兩個參數。
__Shell形式與exec形式有什麼不同呢? __
1 | Unlike the shell form, the exec form does not invoke a command shell. |
在使用RUN指令時,注意事項:
- 如果想要執行的指令很長,可以利用 \ 符號來換行,比較容易閱讀
- 使用exec形式執行時,必需使用JSON array的格式,因此,請使用雙引號
- 每一個RUN就會新增一層資料層,為了減少不必要的資料層,可以利用 && 來串連多個命令
簡單的RUN指令範例
1 | # install packages, only for demo |
CMD (啟動時命令)
功能為容器啟動時要運行的命令, 語法有三種寫法
1 | # exec形式,官方推薦此種方式 |
使用CMD的注意事項:
- Dockerfile中只能有一行CMD,若有多行CMD,則只有最後一行會生效
- 若在建立Container時有帶執行的命令,則CMD的指令會被蓋掉,例如:執行docker run
時,CMD所定義的指令會被執行,但當執行docker run bash時,Container就會執行bash,而原本CMD中定義的值就會覆蓋
簡單的CMD指令範例
1 | CMD echo "This is a test." | wc - |
ENTRYPOINT (啟動預設命令)
和CMD一樣,用來設定映像檔啟動Container時要執行的指令,但不同的是,ENTRYPOINT一定會被執行,而不會有像CMD覆蓋的情況發生,語法有兩種寫法
1 | # exec形式,官方推薦此種方式 |
使用ENTRYPOINT,注意事項:
- Dockerfile中只能有一行ENTRYPOINT,若有多行ENTRYPOINT,則只有最後一行會生效
- 若在建立Container時有帶執行的命令,ENTRYPOINT的指令不會被覆蓋,也就是一定會執行
- 如果想要覆蓋ENTRYPOINT的預設值,則在啟動Container時,可以加上「–entrypoint」的參數,例如:docker run –entrypoint
舉個實例,來看看ENTRYPOINT與CMD的關係,假設Dockerfile中的定義如下:
1 | ENTRYPOINT ["/bin/echo", "Hello"] |
如果是使用docker run -it <image>來啟動Container,那麼輸出的結果為「Hello World」,但如果是用docker run -it <image> Docker來啟動,則輸出結果會變成「Hello Docker」,因為CMD的值被覆蓋掉了
COPY (複製檔案)
複製本地端的檔案/目錄到映像檔的指定位置中
1 | COPY <來源路徑>... <目標路徑> |
使用COPY的注意事項:
- 指令的來源位置可以多個
- 如果目的位置是目錄的話,記得最後要以/結尾,例如:/mypath/
- 目的位置可以是絕對路徑或者相對於WORKDIR定義值的相對路徑
- 若目的位置不存在,會自動建立
COPY的範例
1 | 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 | ADD file1.txt file2.js file3.json ./ |
除非你有自動解壓的需求,不然一般建議會使用「COPY」來加入檔案
EXPOSE (設置埠)
宣告在映像檔中預設要使用(對外)的連接埠
1 | EXPOSE <port> [<port>/<protocol>...] |
EXPOSE範例
1 | # EXPOSE預設的協定是TCP,但如果不是要TCP的話 |
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 | ENV <key> <value> |
ENV範例
1 | ENV demoPATH="/var/log" demoVer="1.0" |
VOLUME (其他容器的掛載點)
1 | VOLUME ["/var/log/"] 或 |
一個 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 | WORKDIR /a |
USER (指定當前用戶)
指定運行Container時的用戶名稱或UID
1 | USER <user>[:<group>] |
USER範例
1 | RUN groupadd -r tester && useradd -r -g tester tester |
USER注意事項:
- 在定義了USER後,則Dockerfile中的RUN、CMD、ENTRYPOINT等指令便會以USER指定的用戶來執行,前提條件是該用戶必需是已存在的,否則指定會失敗
ARG (設置構建參數)
設定在建置映像檔時可傳入的參數,即定義變數名稱以及變數的預設值
1 | ARG <name>[=<default value>] |
ARG範例
1 | ARG Param1 |
建構映像檔案,可利用–build-arg
1 | docker build --build-arg Param1=demo -t myimage:v1 . |
ARG注意事項:
- ARG和ENV的功能類似,都可以設定變數,但是ARG設定的值是供建置映像檔時使用(搭配docker build指令),在Container中是無法使用這些變數的,相反地,ENV的值則可以在Container中存取
ONBUILD
若這個映像檔是作為其他映像檔的基底時,便需要定義ONBUILD指令
1 | ONBUILD [INSTRUCTION] |
ONBUILD範例
A映像檔的Dockerfile定義如下(假設名稱為A-Image)
1 | ...(以上略) |
如果B映像檔是以A映像檔為基底,則A映像檔中的ONBUILD指令就會被觸發
1 | # 以A映像檔為基底 |
ONBUILD注意事項:
- ONBUILD後面接的指令在自建的映像檔中不會被執行,只有當這個映像檔是作為其他映像檔的基底時才會被觸發
如何使用Dockerfile ?
利用Dockerfile來建立我們自己的映像檔,其指令為docker build
1 | # 在目前目錄尋找Dockerfile或dockerfile |
-t:Name and optionally a tag in the ‘name:tag’ format,指定映像檔名稱、標籤
假設Dockerfile在當前目錄下,因此會以.結尾,若是在不同目錄,則可以直接接Dockerfile所在目錄或用-f來指定Dockerfile位置
1 | # 後面接Dockerfile的所在目錄 |
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
註:以上參考了
twtrubiks 的 Docker 基本教學 - 從無到有 Docker-Beginners-Guide文章。
oKong 的 Docker | 第四章:Dockerfile简单介绍及使用文章。
靖.技場 的 Docker – Dockerfile 指令教學,含範例解說文章。
docker buildx build