為什麼要切成 Layer#
把整個檔案系統打成一個大 tarball 也能跑 container,但有兩個痛點:
- 每改一行就要重傳整個檔案系統;網路成本高
- 多個 image 之間若共用 base(例如同樣是 ubuntu),那塊內容會被重複儲存好幾份
Docker 的解法是把 image 切成 Layer。每一層只記錄「相對於下一層的差異」(檔案新增、修改、刪除)。這樣相同的 base layer 可以被多個 image 共用,傳輸時也只需要對 registry 補那幾層缺失的。
Layer 是 image 在 disk 上的儲存單位,也是 registry 傳輸的單位。每一層都有一個獨立的 SHA256 摘要,這就是 Content-Addressable Storage 的根基。
一行 Dockerfile,一層 Layer#
Dockerfile 中能改變檔案系統的指令(RUN、COPY、ADD),每一條都會產出一層新的 layer。其他純 metadata 指令(ENV、LABEL、CMD、EXPOSE)只改 image config,不會新增 filesystem layer,但仍會產生一個中間 image id。
舉例:
FROM alpine:3.19
RUN apk add --no-cache curl
COPY app.sh /usr/local/bin/app.sh
CMD ["/usr/local/bin/app.sh"]build 完之後,可預期的 layer 結構大致是:
- Layer 1:alpine 的 base 檔案系統
- Layer 2:
apk add curl之後新增的二進位、library、index 檔 - Layer 3:
COPY app.sh帶進來的單一檔案 CMD不產生 filesystem layer,只寫進 image config
這個「一行一層」的特性有兩個直接後果:
- 任何寫入動作都會被永久保留在那一層;後續層即使刪除,前面那層仍存在
- 將多個相關指令合併成一個
RUN可以減少層數、也避免「裝了又刪」的中間檔被留在 image 裡
共用 layer 的實際好處#
假設兩個 image 都從 python:3.11-slim 出發:
myapp-api:1.0:再加上 FastAPI、自家程式碼myapp-worker:1.0:再加上 Celery、自家程式碼
下載這兩個 image 時,base 的 python:3.11-slim 各層只會被傳輸與儲存一次。docker pull 時可以看到「Already exists」的 layer,就是因為本機已經有這個 SHA256 對應的 blob。
手動產生新 layer:docker commit#
除了 Dockerfile,還可以直接從一個跑著的 container 產出新 image,這就是 commit:
docker run --name demo -it alpine:3.19 sh
# 在 container 內:
# touch /hello.txt
# echo "hi" > /hello.txt
# exit
docker commit demo demo:v1預期行為:
- 原本的 alpine base layer 不變
- container 在執行時的所有寫入(在它的可寫 layer 內)會被「凍結」成一層新的 read-only layer
- 新 image
demo:v1由 alpine 的若干層 + 這一層新的 diff 組成
docker history demo:v1 應該可以看到最頂端多了一層,CREATED BY 欄位通常是空的或 sh,因為它不是來自 Dockerfile 而是來自手動 commit。
docker commit在實務上不建議當成 build 主流程:缺乏可重現性、沒有歷史記錄。它的價值主要在實驗、debug、或從既有 container 抽出狀態當 base。
Layer 的不可變性#
每一層 layer 一旦建立就是 read-only 的,且以 SHA256 識別。這帶來幾個重要性質:
- 同一個 SHA256 的 layer 在 disk 上只會有一份
- 改任何一個 byte,SHA256 就變,等於是不同的 layer
- 因此 Dockerfile 中愈早期、愈穩定的指令應該放在愈前面,這樣後續修改才不會讓前面的 cache 失效
這解釋了一個常見的優化:把 COPY package.json 與 RUN npm install 放在 COPY . . 之前。只要 package.json 沒變,npm install 那層的 cache 就能命中,build 時間大幅縮短。
層數不是愈少愈好,但不能爆掉#
舊版 Docker(aufs 時代)有層數上限(通常是 127)。overlay2 雖然提高了限制,但層數過多仍會:
- 拉長 mount 時間
- 增加 metadata 查詢成本
- 讓
docker history變得難讀
實務上,把同一個邏輯步驟(例如「安裝依賴 + 清理 cache」)合併成單一 RUN,是平衡可讀性與層數的常見做法。
延伸閱讀#
- Docker 官方文件:Best practices for writing Dockerfiles ↗
- Docker 官方文件:About images, containers, and storage drivers ↗