哈囉,大家好!歡迎來到我們實戰旅程的第五天!
經過 Day 4 的洗禮,我們已經徹底搞懂了 Docker 核心三劍客:Image、Container 與 Registry 之間的關係,甚至還窺探了 Image 分層結構的奧秘。
我們知道,Image 是 Container 的「設計藍圖」,而 Docker Hub 上有成千上萬別人畫好的藍圖。但身為一個追求卓越的工程師,我們怎麼能滿足於只用別人的東西呢?
今天,我們要學習如何使用 Dockerfile 這份「建築說明書」,從無到有,為我們自己的應用程式,量身打造一個專屬的 Image!
在我們動手之前,先來正式認識一下今天的主角。
Dockerfile 是一個純文字檔,裡面會記錄製作一個 Image 的所有步驟。你可以把它想像成一份**「Image 建造說明書」**。
我們在這份檔案中,會用一行行的指令,告訴 Docker:
當我們寫好這份說明書後,只要交給 docker build
這個指令,Docker 就會按照你的指示,一步步地建構出一個標準化的 Image。
有了對 Dockerfile 的基本認識後,我們需要一個實際的應用程式來當作範例。這裡我們準備一個超級簡單的 Node.js Express 網站。
建立專案資料夾:首先,在你的電腦上建立一個新的資料夾,取名為 my-node-app
。
建立 package.json
:在 my-node-app
資料夾中,建立 package.json
。這份檔案用來描述專案需要哪些套件、版本以及啟動指令。
{
"name": "my-node-app",
"dependencies": { "express": "^4.18.2" },
"scripts": { "start": "node server.js" }
}
建立 server.js
:在同一個資料夾中,建立 server.js
,這是我們網站的主程式。
const express = require("express");
const app = express();
const PORT = 3000;
app.get("/", (req, res) => res.send("Hello from my own Docker container!"));
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
產生 package-lock.json
(重要!):package-lock.json
會鎖定所有套件的精確版本,是確保環境一致性的關鍵。請在終端機中,進入 my-node-app
資料夾,執行一次:
npm install
執行後,你會發現資料夾中多了一個 package-lock.json
檔案。
萬事俱備!現在,在 my-node-app
資料夾中,建立一個沒有副檔名、檔名就叫做 Dockerfile
的檔案,然後貼上以下內容。
# 使用 Node.js 官方提供的輕量版映像檔
FROM node:18-alpine
# 建立工作目錄
WORKDIR /app
# 複製 package.json 和 package-lock.json
COPY package*.json ./
# 安裝套件(依照 lock 檔案版本精準安裝)
RUN npm ci
# 複製應用程式原始碼
COPY . .
# 聲明容器內的應用程式會使用 3000 埠來對外溝通
EXPOSE 3000
# 啟動應用程式
CMD ["node", "server.js"]
在專案目錄下執行:
docker build -t <你的 Docker Hub ID>/my-node-app:1.0 .
t
表示加上 tag,格式通常是:
<Docker Hub ID>/<應用名稱>:<版本號>
最後的 .
表示 Dockerfile 在當前目錄。
Docker Hub ID 可以在 Docker Hub 的 Account Settings → Username 找到。這個 ID 必須是英文或數字組成。
Image 建好後,可以用以下指令在本機啟動:
docker run -d -p 8080:3000 <你的 Docker Hub ID>/my-node-app:1.0
d
表示在背景執行p 8080:3000
表示將本機的 8080 port 對應到 container 的 3000 port執行後,打開瀏覽器,訪問 http://localhost:8080
,你就會看到 "Hello from my own Docker container!"。
如果想要讓其他人也能使用這個 Image,可以推送到 Docker Hub。
docker login
docker push <你的 Docker Hub ID>/my-node-app:1.0
推送成功後,在 Docker Hub 的 repository 就能看到剛剛上傳的 Image。
其他人只要執行:
docker pull <你的 Docker Hub ID>/my-node-app:1.0
docker run -d -p 8080:3000 <你的 Docker Hub ID>/my-node-app:1.0
就能跑起來。
還記得我們在 Day 4 提到的「分層結構」嗎?Dockerfile 的寫法,會深刻影響建置效率。
我們的寫法是:
COPY package*.json ./
RUN npm ci
COPY . .
為什麼不一開始就 COPY . .
(把當前專案目錄下的所有檔案全部複製到容器的工作目錄 /app
裡面) 呢?這正是為了善用 建置快取 (Build Cache)!
Docker 會快取每一層的建置結果。當你重新 build 時,如果某一層的指令和檔案內容完全沒變,Docker 就會直接使用快取,跳過執行。
這種寫法確保:當你只修改 server.js 時,前面安裝套件的耗時步驟可以跳過,只重新執行最後的 COPY . .
。
對開發流程來說,這是極其重要的效率優化技巧。
在實際專案裡,我們通常會有 前端 和 後端 各自的 Dockerfile。
前端:把 React/Vue 專案 build 成靜態檔案,再用 Nginx Image 來 serve。
後端:把 Node.js/Java/Spring Boot 應用程式包進 Image,確保後端 API 環境一致。
這樣做的目的:
今天,我們成功地將一個本地專案,轉化為一個標準化的、可攜的、可分享的 Docker Image。
在真實的專案中,前端 (React/Vue) 和後端 (Node.js/Java) 都會各自有自己的 Dockerfile。前端可能會將專案打包成靜態檔案,再用 Nginx Image 來提供服務;後端則會像我們今天一樣,將 API 伺服器打包。最終,不論開發者的本機環境為何,所有人都能運行完全一致的容器環境,徹底告別「我電腦可以跑」的窘境。
既然我們已經能為自己的應用程式蓋好標準化的房子 (Image),那麼下一步,就是學習如何更好地管理住在裡面的房客 (Container),例如:如何讓房客的行李(資料)不會因為搬家而遺失?
在明天的文章中,我們將深入探討運行容器的技巧,特別是 Docker 中最重要也最實用的概念之一 —— Volume (卷)!明天見!