iT邦幫忙

2025 iThome 鐵人賽

DAY 30
0
自我挑戰組

《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》系列 第 30

Day30 - 持續成長學習藍圖 - Docker(理解 Image 與 Build 機制)

  • 分享至 

  • xImage
  •  

昨天已經成功讓 Todo API 在容器裡跑起來。
但我當時有一個疑問——

為什麼有時候我改個程式要重建超久,有時候卻幾秒就好?

今天我就來拆解這背後的原理:Docker 的 Image 是一層一層疊起來的。


1️⃣ 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 就會直接重用快取,這也是為什麼建置速度會差很多。


2️⃣ 來實驗一下快取機制

先 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 機制


3️⃣ 了解 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 .

4️⃣ 查看 Image 與 Layer 結構

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)。


5️⃣ 對外公開埠口

昨天的容器跑起來只能在內部訪問,
要對外提供 API,我們用 -p 指定 port 對映:

docker run -d -p 3000:3000 ts-todo-api:v2

解釋:

  • 左邊(主機):3000
  • 右邊(容器):3000
    代表外部訪問 localhost:3000 就能連進容器內的 Express。

6️⃣ 清理舊的映像與容器

隨著練習越多,Docker 會越胖 XD
這幾個指令要熟:

# 查看所有 image
docker images

# 刪除某個 image
docker rmi <image_id>

# 清掉所有沒用到的
docker system prune -a

7️⃣ 延伸:命名習慣

建議 Image 命名習慣採用:

<project>:<version or env>

例如:

  • ts-todo-api:dev
  • ts-todo-api:v1.0
  • ts-todo-api:test

這樣你未來用 docker-compose 或部署時更清楚。


🎯 今日收穫 / 學習心得

今天的重點不是多跑幾個指令,而是 理解 Docker 為什麼這樣運作

我終於搞懂:

  • 每一行 Dockerfile 都是分層(有快取)
  • 為什麼先 COPY package.jsonnpm ci
  • 怎麼命名 Image 與管理版本
  • -p 對外暴露 API
  • docker history 可以看到整個建構過程

感覺 Docker 像是一個有層次的冰淇淋塔 🍦
我只要改最後一層(程式碼),前面的基底(系統與依賴)就能被快取重用。


上一篇
Day29 - 持續成長學習藍圖 - Docker(Dockerfile)
下一篇
Day31 - 持續成長學習藍圖 - Docker(資料持久化與 Volume)
系列文
《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》34
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言