🐳 Docker 映像檔瘦身之旅的開端:透過 docker history 揭開其「家譜」

明明只啟動了一個輕量級應用程式,檢查映像檔大小卻發現它輕鬆超過了 1GB。您可能會疑惑:「我到底加了什麼?」感到不知所措。這時候,我們就需要揭開映像檔的「家譜」,一探究竟。

Docker 層次瘦身

「你到底吃了什麼,怎麼變得這麼胖?」

Docker 映像檔就像一層層堆疊的洋蔥皮(層次)。docker history 這個指令就像是 X 光機,能夠一層層剝開這些「洋蔥皮」,找出讓映像檔臃腫的元兇。

其基本用法非常簡單。

# 基本用法
docker history [選項] <映像檔名稱:標籤>

# 範例:查看我的應用程式歷史記錄
docker history my-app:latest

執行此指令後,會從映像檔的最新層次開始,一直追溯到基礎映像檔,以逆序方式完整呈現。

IMAGE          CREATED          CREATED BY                                      SIZE
a6215f271958   5 minutes ago    /bin/sh -c #(nop)  CMD ["/bin/sh"]               0B
<missing>      7 weeks ago      /bin/sh -c #(nop) ADD file:f28242cf608f6...     7.81MB

請仔細觀察其中的 SIZE 欄位。如果應該是 0B 的地方卻顯示數百 MB,那麼它就是我們瘦身的目標。憑經驗來看,總會有些「可疑之處」。

💡 有用提示:--no-trunc

預設輸出會截斷過長的指令。此時,加上 --no-trunc 選項,就能完整顯示整個指令,這對於精確分析至關重要。

docker history --no-trunc my-app:latest


實戰案例:「兇手就是 chown

讓我分享一個我親身經歷的真實案例。當時我建置了一個 150MB 的專案檔案,但映像檔大小卻異常地超過了 300MB。而元兇,竟然是 Dockerfile 中兩行看似再普通不過的程式碼。

[低效率做法:產生 2 個層次]

# 1. 先複製檔案 (預設以 root 權限複製)
COPY . .

# 2. 為安全性考量變更所有權 (以獨立指令執行)
RUN chown -R appuser:appgroup /app

在建置完畢後,我用 docker history 檢查,結果發現了以下內容:

IMAGE          CREATED BY                                        SIZE
<layer_id_2>   /bin/sh -c chown -R appuser:appgroup /app         150MB  <-- (問題所在!)
<layer_id_1>   /bin/sh -c #(nop) COPY dir:abc in /app            150MB

這就揭示了 Docker 層次的可怕之處。在第一步中,複製 150MB 的檔案會產生一個層次。然而,當在第二步執行 chown 時,Docker 會想:「咦?檔案資訊(所有權)變了?那麼,我必須在變更後的狀態下再建立一個層次!」於是,它會直接複製原有的 150MB 內容,堆疊出一個新的層次。

結果,內容物完全相同,卻因為所有權不同,導致檔案重複儲存了兩次,映像檔大小也隨之翻倍。


解決方案:一行程式碼搞定瘦身!

解決方案很簡單。只要在複製檔案時,一開始就指定好所有權,將層次合併為一個即可。

[高效做法:產生 1 個層次]

# 複製檔案的同時,也指定所有權!
COPY --chown=appuser:appgroup . .

將 Dockerfile 修改成這樣後,再次執行 docker history,結果將會是戲劇性的。

IMAGE          CREATED BY                                               SIZE
<layer_id_1>   /bin/sh -c #(nop) COPY --chown=appuser... dir:abc       150MB

看到了嗎?只產生了一個層次,映像檔大小也恢復到輕盈的 150MB。僅僅將兩行指令合併成一行,容量就減少了一半。


結論:建置後務必檢查「歷史記錄」!

請務必記住,Docker 映像檔的原則是每執行一行指令,就產生一個層次

就像編寫程式碼後需要進行測試一樣,建置完成後,請養成習慣執行 docker history --no-trunc。當您不斷追問「這個 apt-get 為什麼這麼臃腫?」「這個層次為什麼要特意分開?」這些問題時,不知不覺中,您將會建構出一個極其輕巧且高效的系統。

因為不必要的層次,正是系統效能的敵人!


這篇文章對您有幫助嗎? 如果有,請不吝點個讚!

也請閱讀相關文章。

相關文章

徹底理解 Docker 共享記憶體 (shm_size 與 ipc)

Docker:不共享網路,透過主機埠實現容器間通訊

Docker 磁碟區 (Volume) 複製,為何必須這樣做?