Docker 學習筆記 (四) — 如何撰寫Dockerfile

Docker 的問世,翻轉了人們對用環境部署與開發的習慣,學會 Docker 絕對是一項不虧本的投資

Les Lee
一個小小工程師的隨手筆記
7 min readMay 10, 2019

--

上一篇 我們已經講述了 Docker 常用指令的使用方式。

本篇我們要來討論一下 Docker 創建 Image 的另一個方法:使用 Dockerfile

Dockerfile 是一個以 YAML 語法格式撰寫的純文字檔案。

用以描述 Docker 產生鏡像檔所需要的內容、以及要做的事情。

Docker 預設會找尋目錄中的“Dockerfile” 這個名字的檔案作為要產生 Image 的內容,因此不建議自行命名(當然你要建立自己命名的 Dockerfile 也行,只是在 docker build 時就要指定它要讀哪個檔案)。

詳細的 Dockerfile 文件撰寫規範可以參考 Docker 官方的 Dockerfile Document

本篇將以一個範例作為 Dockerfile 的講解。

以上的 Dockerfile 範例描述了我們在 Build Image 時要做哪些事情。

  1. 使用 python-3.7.3-stretch 作為基底鏡像
  2. 設置檔案當前目錄為 app
  3. 將專案的 requirement.txt, gunicorn.py 複製到 docker 內。
  4. 執行 pip install
  5. 宣告開放 5000 PORT。
  6. 設置環境變數。
  7. 設定 Container 啟動時要執行的指令。

以下我們將逐行講解這些指令的詳細用法。

#:使用 # 代表註解

文件可以使用 # 來進行註解。

FROM

以哪個 Image 為基底進行改良

格式: FROM <image name>FROM <image name>:<tag>

一般來說我們創建一個自己的 Image,都是希望 Docker 環境能夠直接執行專案。

我們不希望從架設環境開始創建 Docker Image,因此大多時候,我們會直接從 DockerHub 找尋已經架好環境的 Image 作為基底,創建自己的 Image。

From 關鍵字會先從本機的 Docker image 中找尋符合的 Image,若找不到則從 DockerHub 上下載。

以我們這邊的例子便是以 Python:3.7.3-stretch 作為基底 Image 來建置我們自己的 Image。

WORKDIR

設定當前工作目錄。

格式: WORKDIR <path>

使用了 WORKDIR Docker 會將指定的目錄設定為當前的工作目錄。之後使用到的各項指令諸如:RUN、CMD、ENTRYPOINT、COPY…等,都會以此目錄為主。

另外,如果此目錄並不存在,WORKDIR 會自動幫你創立

RUN

執行命令。

格式: RUN <command>

RUN 用來執行 Shell 指令,例如本文範例便是使用 RUN 調用 pip 安裝 python 的相關套件。

這邊有一點要值得注意的是,Dockerfile 中的每一個指令都是啟動一個 container 、執行命令、之後以 Commit 的方式提交修改,直到完成整個 Image。

很多剛接觸 Dockerfile 的使用者,會將 RUN 當成一般的 Shell 腳本撰寫,這樣的理解就有可能踩到以下這個例子的坑。

RUN cd /app
RUN echo "hello" > world.txt

這種寫法,在 build Dockerfile 時,會輸出「找不到 /app/world.txt」的錯誤或是 world.txt 的內容不是 hello,其原因便是因為兩條指令存在於不同的 container,兩者互不影響。

COPY

複製「來源文件\目錄」到的 Image 中的「文件\目錄」中。

格式:COPY [--chown=<user>:<group>] <source path>... <dist path>

dist path 可以是 Container 內的絕對路徑,也可以是相對於 WORKDIR 的相對路徑。與 WORKDIR 一樣,若是當下 Image 中沒有此目錄,則複製文件前 Docker 會自動建立缺少的目錄。

另外,使用 COPY 會保留來源數據的各種屬性,諸如:讀寫權限執行權限文件變更時間…等等。若希望更改文件的所屬 user 或是 group,可以在 COPY 時加入--chown=<user>:<group> 來自訂。

補充:

與此指令相仿的還有 ADDADD 可以當作是 COPY 的強化版。

格式:ADD [--chown=<user>:<group>] <source path>... <dist path>

ADD 允許我們的 source path 是一個 URL。

此時, Docker 將會自動去網路上下載這個 URL 的文件到 dist path 中,並且文件權限自動設置為 600

另外,如果下載的是一個壓縮檔,ADD 會自動將其解壓縮

儘管 ADD 是如此方便,但也因為其過多的預設功能使其在 Docker 的官方文檔中推薦盡可能使用 COPY 而非 ADD。除非在需要自動解壓縮檔案的場合才使用。

CMD

CMD 指令與 RUN 相似,用於指定 Container 的啟動時首要執行的命令。

格式:CMD <command>CMD [“command name”, argv1, argv2, …]

以我們的範例來說,便是當 Container 啟動時,便會先行執行 gunicorn -c gunicorn.py run:app 這行 shell script。

值得一提的是,CMD 可以在 RUN 一個 Container 時,以 -it <其他指令> 來取代掉。

例如:若我們今天以 docker run -it test_container bash 啟動 Container 的話,我們將會直接進入到 bash 而非執行 gunicorn

ENV

設置環境變數

格式:ENV <key> <value> 或是 ENV <key1>=<value1> <key2>=<value2> ...

若專案中有需要使用到環境變數,則可以使用此指令設置環境變數。

EXPOSE

開放 PORT。

格式:Expose <PORT1> <PORT2> …

EXPOSE 指令是宣告 Container 運行時對外可以使用哪些 PORT。

這只是一個宣告,在運行時並不會因為這個宣告就讓專案開啟這些 PORT 的服務,一切還是以專案怎麼撰寫為主。

要搞清楚的是,在運行 Container 時,所使用的 -p <host port>:<container port> 是映射 host 的 port 與 container 的 port,而 EXPOSE 僅是宣告 Container 打算使用什麼 PORT 。並不會自動映射到 host 的 port。

結語

本節中,我們以一個範例講述的 Dockerfile 如何撰寫。

並且講述了每個指令的意義與使用方法。

若是需要更加深入的撰寫教學,可以查看官網、或是此文隨附的參考目錄。

應該可以對於 Dockerfile 有更深入的理解。

下一篇我們將以一個小範例作為 Docker 操作的實戰講解

參考條目

--

--

Les Lee
一個小小工程師的隨手筆記

對新事物總是興緻勃勃、嘗試新技術、解決新挑戰; 也時常陷入許多無謂的思索,卡在其中得不出答案。