昨天已經成功讓 Todo API 在容器裡跑起來。
但我當時有一個疑問——
為什麼有時候我改個程式要重建超久,有時候卻幾秒就好?
今天我就來拆解這背後的原理:Docker 的 Image 是一層一層疊起來的。
Docker 在 build 的時候,其實是根據每一行 Dockerfile,建立出一層 Layer。
FROM node:18-alpine # 第 1 層:基底環境
WORKDIR /app # 第 2 層:設定工作目錄
COPY package*.json ./ # 第 3 層:加入 package.json
RUN npm ci # 第 4 層:安裝依賴
COPY . . # 第 5 層:加入程式碼
CMD ["npm", "run", "dev"] # 第 6 層:啟動指令
每一層都是一個快取點,只要前一層沒有變,Docker 就會直接重用快取,這也是為什麼建置速度會差很多。
先 build 一次:
docker build -t ts-todo-api:v1 .
輸出中你會看到:
=> [5/6] COPY . . 0.5s
=> [6/6] CMD ["npm", "run", "dev"] 0.0s
現在你改個程式內容(例如 src/index.ts
),再 build:
docker build -t ts-todo-api:v2 .
你會發現:
=> [1/6] FROM node:18-alpine 0.0s (cached)
=> [2/6] WORKDIR /app 0.0s (cached)
=> [3/6] COPY package*.json ./ 0.0s (cached)
=> [4/6] RUN npm ci 0.0s (cached)
=> [5/6] COPY . . 0.6s
前面幾層都用了快取,只有改動程式的那層重建。
💡 這就是 Docker 的 layer cache 機制。
docker build
常用參數指令 | 說明 |
---|---|
docker build -t <名稱> |
幫 Image 命名(tag) |
docker build . |
從目前目錄的 Dockerfile 建立映像 |
docker build -f ./Dockerfile.dev . |
指定 Dockerfile |
docker build --no-cache . |
不使用快取(完全重建) |
例子:
docker build -t ts-todo-api:dev .
docker build -t ts-todo-api:v2 --no-cache .
docker images
會看到:
REPOSITORY TAG IMAGE ID CREATED SIZE
ts-todo-api v2 8e912f9c2f7a 1 minute ago 220MB
node 18-alpine f6a7a8d26f5c 2 weeks ago 180MB
查看詳細結構:
docker history ts-todo-api:v2
輸出會列出每一層的來源與大小,像是:
IMAGE CREATED CREATED BY SIZE
8e912f9c2f7a 1 minute ago CMD ["npm" "run" "dev"] 0B
<missing> 1 minute ago COPY . . 40MB
<missing> 1 minute ago RUN npm ci 120MB
<missing> 1 minute ago COPY package*.json ./ 1kB
...
這就是完整的 分層紀錄。
每一層都是唯讀的,容器啟動時會疊加一層可寫層(writable layer)。
昨天的容器跑起來只能在內部訪問,
要對外提供 API,我們用 -p
指定 port 對映:
docker run -d -p 3000:3000 ts-todo-api:v2
解釋:
localhost:3000
就能連進容器內的 Express。隨著練習越多,Docker 會越胖 XD
這幾個指令要熟:
# 查看所有 image
docker images
# 刪除某個 image
docker rmi <image_id>
# 清掉所有沒用到的
docker system prune -a
建議 Image 命名習慣採用:
<project>:<version or env>
例如:
ts-todo-api:dev
ts-todo-api:v1.0
ts-todo-api:test
這樣你未來用 docker-compose
或部署時更清楚。
今天的重點不是多跑幾個指令,而是 理解 Docker 為什麼這樣運作。
我終於搞懂:
COPY package.json
才 npm ci
-p
對外暴露 APIdocker history
可以看到整個建構過程感覺 Docker 像是一個有層次的冰淇淋塔 🍦
我只要改最後一層(程式碼),前面的基底(系統與依賴)就能被快取重用。