歡迎來到 Docker 的進階玩法——撰寫 Dockerfile!在前兩篇文章中,我們聊過 Docker 的核心架構(咖啡機的運作原理)和常用指令(如何點單與沖咖啡)。這次,我們要更進一步,自己動手調配「咖啡配方」,也就是撰寫 Dockerfile,讓你能打造專屬的 Docker Image。一樣會沿用「咖啡機」的比喻,若還沒閱讀過前文的讀者,可以先至上篇閱讀 或 前篇閱讀 看看。準備好了嗎?讓我們開始調配吧!
什麼是 Dockerfile?
Dockerfile 就像是咖啡店裡的「沖泡配方單」。它告訴 Docker 如何從一堆原料(Base Image、程式碼、依賴)一步步沖出一杯完美的咖啡(Container)。有了 Dockerfile,你可以:
- 定義咖啡機的基礎環境(例如用哪款咖啡機)。
- 加入自己的原料(程式碼、設定檔)。
- 設定沖泡步驟(安裝依賴、啟動應用)。
最終,這個配方會被打包成一個 Image,就可以隨時能用 docker run
沖出來。
基本結構:咖啡配方的組成
假設你要做一杯「Node.js 拿鐵」,以下是撰寫 Dockerfile 的基本指令:
FROM:選一台咖啡機
指定 Dockerfile 的基礎映像檔(Base Image),作為構建的起點。所有的後續指令都基於這個映像檔執行。
語法:FROM <image>[:<tag>] [AS <name>]
<image>
:映像檔名稱,例如node
、ubuntu
。<tag>
:版本標籤,例如18
、latest
(不指定會預設為latest
)。AS <name>
:可選,用於多階段構建時命名這個階段。
1 | FROM node:18 |
就像選擇一台「Node.js 品牌的咖啡機」,版本是 18,裡面已經裝好了基本的沖泡工具(Node.js 環境)。
WORKDIR:設定沖泡檯面
設定工作目錄,將本機的檔案或目錄複製到容器內的指定路徑,所有後續操作都在這裡進行。
1 | WORKDIR /app |
就像在咖啡店裡選一個乾淨的檯面,準備開始調配。
COPY:加入原料
把本地的檔案(程式碼、設定檔)複製到容器內的工作目錄。
語法:COPY <src> <dest>
<src>
:來源路徑(本機檔案)。<dest>
:目標路徑(容器內部)。
1 | COPY . . |
把你的咖啡豆(app.js
)、糖(package.json
)搬到檯面上,準備開始調配。
ADD:拿食材(進階版)
類似 COPY,但支援自動解壓縮和從 URL 下載文件。
語法:ADD <src> <dest>
:主機上的文件、目錄或 URL。 :容器內的目標路徑。
1 | ADD https://example.com/file.tar.gz /app/ |
ENV:設定沖泡參數
設置容器內的環境變數。
語法:ENV <key>=<value>
1 | ENV APP_PORT=8080 |
就像設定咖啡機的「水溫」或「沖泡時間」,這裡設定 APP_PORT=8080 作為應用程式的運行端口。
RUN:準備沖泡環境
執行指令,通常用來安裝依賴,並將結果保存到映像檔中。
語法:
- Shell 格式:
RUN <command>
(例如RUN npm install
)。 - Exec 格式:
RUN ["executable", "param1", "param2"]
(例如RUN ["apt-get", "update"]
)。
1 | RUN npm install |
像是把咖啡豆磨碎、加入濾紙、清洗咖啡機,這些都是準備沖泡的步驟。這些動作在打包配方(Image)時完成,而不是端上桌時才做。
注意
RUN
在docker build
時執行,每次執行都會生成一個新的映像層(Layer)。- 盡量將多個命令用
&&
合併(例如RUN apt-get update && apt-get install -y curl
),減少層數,節省空間。 - 與後面的
CMD
不同,RUN
不影響容器運行時的行為。
CMD:按下開始鍵
指定容器啟動時要執行的命令。每個 Dockerfile 只能有一個 CMD,如果有多個,只有最後一個有效。
語法:
- Exec 格式(推薦):
CMD ["executable", "param1", "param2"]
。 - Shell 格式:
CMD command param1 param2
。 - 參數格式:
CMD ["param1", "param2"]
(搭配ENTRYPOINT
使用)。
1 | CMD ["node", "app.js"] |
指定容器啟動時要執行的命令。
就像告訴咖啡機:「用這些原料沖一杯咖啡吧!」
注意
CMD
在docker run
時執行,且可以被命令列參數覆蓋(例如docker run my-image bash
會取代CMD
)。- 如果沒指定
CMD
,容器可能無法運行(除非有ENTRYPOINT
)。 - 與
RUN
的區別:RUN
是構建時跑,CMD
是啟動時跑。
ENTRYPOINT:咖啡機的預設模式
設定容器啟動時的預設執行程式,類似 CMD
,但更像一個固定的「入口點」。它不會被 docker run
的參數輕易覆蓋。
語法:
- Exec 格式(推薦):
ENTRYPOINT ["executable", "param1", "param2"]
。 - Shell 格式:
ENTRYPOINT command param1 param2
。
1 | ENTRYPOINT ["node"] |
容器啟動時固定執行 node
,而 app.js
是預設參數,可以被 docker run
覆蓋(例如 docker run my-image index.js
變成 node index.js
)。
就像設定咖啡機的「預設模式」為「沖泡模式」(node
),而 CMD
是你給的配方(app.js
)。你可以臨時換配方,但模式不會變。
注意
ENTRYPOINT
和CMD
搭配使用時,CMD
提供預設參數,ENTRYPOINT
提供執行程式。- 如果只有
ENTRYPOINT
,沒有CMD
,容器啟動時不會有預設行為,除非在docker run
提供參數。 - Shell 格式的
ENTRYPOINT
會忽略CMD
和docker run
的參數,建議用 Exec 格式。
EXPOSE:標示咖啡出口
告訴 Docker 這個容器預計使用哪些端口,並作為文件提示。它不會自動映射端口,實際映射仍需靠 docker run -p
。
語法:EXPOSE <port>[/<protocol>]
<port>
:端口號。<protocol>
:通訊協定(預設為tcp
,可選udp
)。
1 | EXPOSE 3000 |
表示容器內的應用會使用 3000 端口。
就像在咖啡機上貼個標籤:「咖啡從 3000 號出口出來哦!」但你還是得自己把杯子(本機端口)拿到出口(-p 3000:3000
)接咖啡。
實戰演練:打造 Node.js 拿鐵
讓我們實際寫一個簡單的 Node.js 應用,並用 Dockerfile 打包。
準備程式碼
創建一個資料夾(例如 coffee-app
),裡面放以下檔案:
1 | // app.js |
1 | // package.json |
撰寫 Dockerfile
在同一個資料夾裡創建 Dockerfile
:
1 | # 選一台 Node.js 18 的咖啡機 |
打包成 Image
打開終端機,進入 coffee-app
資料夾,執行:
1 | docker build -t my-coffee . |
- 比喻:就像把配方單交給店長(Docker Daemon),說:「幫我把這份配方打包好!」
- 結果:生成一個名叫
my-coffee
的 Image。
4:沖一杯咖啡
1 | docker run -d -p 3000:3000 my-coffee |
- 比喻:按下咖啡機的開始鍵,把咖啡送到 3000 號桌。
- 結果:瀏覽
http://localhost:3000
,會看到「Enjoy your Docker Coffee!」。
進階技巧:讓咖啡更好喝
多階段構建:減肥版咖啡
如果你的 Image 太大,可以用多階段構建。例如:
1 | # 第一階段:準備原料 |
- 比喻:就像先把咖啡豆磨好(第一階段),然後只用輕巧的咖啡機沖泡(第二階段),減少浪費。
ENV:加入秘密調味料
1 | ENV PORT=3000 |
設定環境變數。
像是偷偷加點糖漿,讓咖啡更有風味。
EXPOSE:標示咖啡出口
1 | EXPOSE 3000 |
告訴別人這個容器會用哪個端口(只是提示,不強制)。
就像在咖啡機上貼個標籤:「咖啡從 3000 號出口出來哦!」
常見問題與注意事項
檔案大小太大?
- 檢查是否複製了不必要的檔案。用
.dockerignore
排除(例如node_modules
)。 - 比喻:別把整個原料倉庫搬進咖啡機,只帶需要的就好。
- 檢查是否複製了不必要的檔案。用
CMD vs RUN?
RUN
是構建時執行(磨豆子),CMD
是啟動時執行(沖咖啡)。- 比喻:準備原料和端上桌是兩回事。
忘了加 WORKDIR?
- 指令會在根目錄(
/
)執行,可能導致混亂。 - 比喻:沒選檯面,原料會散落一地。
- 指令會在根目錄(
撰寫 Dockerfile 就像調配咖啡配方:從選咖啡機(FROM
)、準備原料(COPY
)、設定步驟(RUN
),到最後按下開始鍵(CMD
)。學會這些,你就能打造屬於自己的 Docker Image,隨時沖出一杯穩定的「程式咖啡」!