目錄

本文深入說明 Dockerfile 最佳實務,從 Docker Image 的分層原理、快取策略到 Multi-stage builds 的實戰寫法,完整解析如何打造小而快、安全且可維運的容器映像檔。內容涵蓋效能優化、映像檔瘦身、安全設定與生產環境常見踩雷,適合 Docker 新手與正式環境部署使用。

在 Docker 的世界裡,真正影響「部署速度、成本、穩定性與安全性」的核心,不是你用什麼容器平台,而是你怎麼做 Docker Image、怎麼寫 Dockerfile

同一個應用程式,可能有人打出 1.2GB 的映像檔,啟動慢、掃描漏洞滿天飛;也可能有人用最佳實務做到 80MB、啟動快、快取命中率高、供應鏈風險也更低。

這篇會用「實戰導向」方式,帶你從基礎概念一路到企業可用的最佳實務:包含分層快取、multi-stage builds、最小化映像檔、非 root、SBOM / 簽章、以及 CI/CD 常見踩雷修法。

Docker Image 與 Dockerfile:你要先掌握的底層邏輯

Dockerfile 是用來描述「如何建置映像檔」的規格文件,而 Docker Image 則是依據 Dockerfile 所產生、可被部署與執行的成果。Docker Image 採用分層(Layer)架構,每一個 Dockerfile 指令通常都會形成一層,這種設計直接影響建置速度、快取命中率、映像檔大小與後續維運成本。理解 Dockerfile 如何轉換為多層 Image,是實踐 Dockerfile 最佳實務、進行效能與安全優化的關鍵基礎。

Docker Image 是什麼

Docker Image 是一個不可變(Immutable)的應用程式映像檔,內含作業系統基底、執行環境、相依套件與應用程式本身,可被一致地部署到任何支援 Docker 的環境中。Docker Image 採用分層(Layer)架構,每一層都可被快取與重用,這使得映像檔的建置效率、體積大小與部署速度,直接受到建置方式與分層設計的影響。

Docker Image(映像檔)可以理解成可攜式的應用程式包,裡面包含:

  • OS/Runtime 基底(例如 Debian/Alpine)
  • 你的應用程式程式碼
  • 依賴套件(node_modules、pip wheels、system libs)
  • 啟動指令與環境設定

映像檔本質是由很多層(layers)組成,每一個 Dockerfile 指令(如 RUNCOPY)通常會形成一層。這個「分層」是效能與成本的關鍵,因為:

  • 分層快取命中 → build 很快
  • 層改變 → 之後的層都重建 → build 變慢
  • 層越多、越肥 → pull 慢、部署慢、CDN/Registry 成本高

Dockerfile 是什麼

Dockerfile 是一個用來定義 Docker Image 建置流程 的設定檔,透過一系列具備順序性的指令,描述基底映像、相依套件、程式碼複製方式與容器啟動行為。Docker 會依照 Dockerfile 的指令逐層建立映像檔,每一層都可被快取與重用,因此 Dockerfile 的撰寫方式,會直接影響映像檔大小、建置效率、安全性與生產環境的可維運性。

一份設計良好的 Dockerfile,將直接決定下列關鍵成果:

  • 建置流程是否具備可重現性(Reproducibility)
  • 映像檔體積是否最小化,並具備良好的快取命中率
  • 映像檔是否符合基本安全原則,降低供應鏈與權限風險
  • 部署至正式環境時,是否具備穩定性與可維運性

如果 Dockerfile 設計不良,常見會導致以下問題:

  • 映像檔體積持續膨脹
    缺乏正確的分層策略或 .dockerignore,容易將不必要的檔案、快取與建置產物打包進映像檔,造成下載慢、部署成本增加。

  • 建置速度緩慢,CI/CD 效率低落
    不當的 COPY 順序或未善用快取機制,會使每次程式碼變動都觸發完整重建,導致 CI 建置時間顯著增加。

  • 安全風險提高
    映像檔中可能包含多餘的系統工具、開發套件,甚至以 root 權限執行服務,擴大攻擊面並增加供應鏈風險。

  • 正式環境行為不可預期
    未固定 base image 版本、建置流程不可重現,可能導致同一份 Dockerfile 在不同時間或環境產生不同行為,增加除錯與維運難度。

  • 後續維運與擴展成本上升
    Dockerfile 若缺乏結構與一致性,當專案成長、服務拆分或導入 Kubernetes 時,會成為技術債,拖慢整體架構演進。

極簡實作範例:從 Dockerfile 到可部署 Image

在理解 Docker Image 與 Dockerfile 的基本概念後,最有效的學習方式,是透過一個結構單純、但符合實務流程的範例,實際體驗映像檔從建置到執行的完整過程。

以下示範以 Nginx 靜態網站 作為範例,刻意選擇最小可行配置(Minimal Viable Example),目的是讓讀者專注於 Dockerfile 的角色、映像檔的生成方式,以及容器啟動後的實際行為,而非被過多應用程式細節干擾。

這個範例同時符合正式環境中常見的使用情境,例如靜態網站、反向代理或基礎服務入口,也能作為後續導入 Volume、Docker Compose 或多服務架構的基礎。

Dockerfile(極簡)

				
					FROM nginx:alpine
COPY ./site/ /usr/share/nginx/html/
EXPOSE 80
				
			

直接跑起來

				
					docker build -t demo-nginx .
docker run --rm -p 8080:80 demo-nginx

				
			

./site/ 放一個 index.html,開 http://localhost:8080 就看到結果。

這個範例重點是:你已經完成 image buildcontainer run對外服務 的完整閉環。

Dockerfile 最佳實務:效能優化的核心是 " 快取策略 "

Docker 映像檔的建置效能,關鍵並不在於硬體資源,而在於 Dockerfile 是否能有效利用分層快取機制。透過正確安排指令順序、穩定依賴層與避免不必要的快取失效,可大幅縮短建置時間、降低 CI/CD 成本,並提升整體部署效率。
Dockerfile 的效能優化,99% 都在解決快取命中率層的穩定性

原則 A:依賴先裝、程式後拷貝

以 Node.js 為例,錯誤寫法如下(每次改一行程式都讓 npm reinstall):

				
					COPY . .
RUN npm ci
				
			

正確寫法是:先 COPY lock 檔與 package.json,再安裝依賴,最後才 COPY 程式碼

				
					COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
				
			

這樣你改程式碼時,npm layer 不會重跑,CI build 速度會差非常多。

原則 B:少用會破壞快取的操作

常見破壞快取的指令包括:

  • COPY . . 放太前面
  • RUN apt-get update 沒與 install 合併
  • 在同一層做「會改動大量檔案」的行為(例如 build 產物、測試報告)

原則 C:以「可預期」的方式產生輸出

建議確保 build 輸出一致:

  • 使用 lock 檔(package-lock.json / pnpm-lock.yaml / poetry.lock
  • 固定 base image tag 或 digest(後面會講)
  • 避免 build 過程依賴外部不穩定資源

Multi-stage builds:讓映像檔變小的最強招

Multi-stage builds 是一種 Dockerfile 設計技巧,透過將「建置階段」與「執行階段」明確分離,只在最終映像檔中保留實際執行所需的檔案與相依環境。此作法能有效移除編譯工具與多餘依賴,顯著降低映像檔體積,同時改善部署效率並減少正式環境的安全風險。

Multi-stage builds 的觀念很簡單:

  • 第一階段:裝編譯工具、建置應用(很肥沒關係)
  • 第二階段:只拷貝「執行所需的產物」到乾淨 runtime image(很瘦)

範例:Node.js(build + runtime 分離)

				
					# 1) builder
FROM node:20-bookworm AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 2) runtime
FROM node:20-bookworm-slim
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]

				
			

你會得到:

  • runtime image 沒有 build tools(gcc、make)
  • 沒有 dev dependencies
  • 启动更快、攻擊面更小、掃描漏洞通常也更少

映像檔 " 瘦身 " 實戰:小一點,就是快、穩、省

Docker 映像檔的體積,會直接影響建置時間、部署速度、資源使用與整體維運成本。透過精簡 base image、移除不必要的檔案與相依套件,並善用分層與建置策略,可有效降低映像檔大小,進而提升部署效率、系統穩定性與正式環境的可控性。

映像檔瘦身的目的,在於減少不必要的內容與相依,讓 Docker Image 在建置、部署與維運各階段都更高效且更穩定。

技巧 1:選對 base image(別盲目 Alpine)

很多人覺得 Alpine 一定最小,但實務上不一定:

  • 有些套件在 Alpine 需要額外相依(musl vs glibc)
  • 可能導致相容性問題(尤其是某些 Node / Python native module)
  • Debug 也較麻煩

建議策略:

  • 需要穩定相容:debian:bookworm-slimubuntu:24.04
  • 追求極小且確定相容:alpine
  • 高度安全 / 最小化:distroless(但除錯要有策略)

技巧 2:合併 apt 指令並清 cache

錯誤:

				
					RUN apt-get update
RUN apt-get install -y curl
				
			

正確:

				
					RUN apt-get update \
 && apt-get install -y --no-install-recommends curl \
 && rm -rf /var/lib/apt/lists/*
				
			

技巧 3:避免把不必要的東西 COPY 進 image

一定要用 .dockerignore。最常見漏掉的:

  • .git/
  • node_modules/
  • venv/
  • dist/(如果是 build 產物)
  • *.log

範例:

				
					.git
node_modules
dist
*.log
.DS_Store
				
			

安全最佳實務:從 " 可跑 " 到 " 可上線 " 的差別

容器在開發環境中能正常執行,並不代表其已具備正式上線的安全條件。透過落實最小權限原則、固定基底映像版本、移除不必要元件與加入健康檢查等安全實務,才能降低攻擊面與營運風險,使容器真正符合生產環境的安全與穩定要求。

正式上線的容器,必須在安全性、權限控管與可預期行為上具備明確規範,而不僅僅是能成功啟動與執行

不要用 root 跑服務

在容器中以 root 權限執行服務,會在發生漏洞或入侵時放大影響範圍,增加系統被濫用或橫向移動的風險。透過以非特權使用者執行應用程式,可有效落實最小權限原則,降低安全事件對正式環境造成的衝擊,並符合多數生產環境與合規要求。

在生產環境中,以非 root 使用者執行容器服務是降低風險與落實最小權限原則的基本安全要求。

很多預設 image 會以 root 執行,這在生產環境是高風險。做法:

  • 建立使用者
  • 切換到非 root
				
					RUN useradd -m appuser
USER appuser
				
			

若是 Alpine:

				
					RUN adduser -D appuser
USER appuser
				
			

固定 base image(避免供應鏈飄移)

固定 base image 的版本或雜湊值(digest),可確保 Docker Image 在不同時間與環境中具備一致的建置結果,避免因上游映像更新而產生不可預期的行為差異。此作法有助於提升建置可重現性,降低供應鏈風險,並使問題追蹤與安全稽核更具可控性。

透過固定 base image 版本,可避免上游變動造成映像行為漂移,確保建置結果的穩定性與可重現性。

如果你寫:

				
					FROM node:20
				
			

它會隨時間變動,可能某天上游更新造成不可預期差異。

較佳做法:

  • 固定到 minor / distro:node:20-bookworm-slim
  • 或更嚴謹:用 digest(可重現性最高)

健康檢查(Healthcheck)

健康檢查(Healthcheck)用於判斷容器內的服務是否處於可正常提供功能的狀態,而不僅僅是程序是否仍在執行。透過定期檢測指定的端點或指令回應,系統可即時辨識異常容器,並配合重新啟動、流量切換或編排機制,提升正式環境的穩定性與可用性。

Healthcheck 透過持續檢測服務狀態,確保容器在正式環境中能被正確判定為可用或異常。

容器起得來不代表服務正常。加上健康檢查可改善自動重啟與負載均衡判斷:

				
					HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -fsS http://127.0.0.1:3000/health || exit 1
				
			

祕密不要寫進 Dockerfile(尤其不要 COPY .env)

在 Dockerfile 中直接寫入或複製機密資訊(例如 API Key、Token、密碼或 .env 檔案),會使這些敏感資料永久嵌入映像檔層中,一旦映像檔被推送至 Registry、分享給他人或用於多個環境,機密即可能遭到未授權存取,形成嚴重的資安風險。由於 Docker Image 具備可重用與可散佈的特性,任何在建置階段寫入的祕密資訊,都難以在事後完全移除或追蹤影響範圍。

在正式環境中,機密資訊應與映像檔建置流程明確分離,改以執行階段注入的方式提供,例如透過環境變數、容器編排平台的 Secret 機制,或集中式的祕密管理服務。此作法不僅能降低供應鏈外洩風險,也有助於實現不同環境之間的權限隔離、機密輪替與合規管理。

為避免機密隨映像檔散佈並形成供應鏈風險,敏感資訊不應寫入 Dockerfile 或建置產物,而應於執行階段以安全機制注入。

正確方式:

  • 以環境變數注入(runtime)
  • 或使用 secret manager(K8s secret、Vault、CI secret)
  • build 階段需要 token:使用 BuildKit secret(後面會提)

BuildKit 與進階快取:讓 CI 建置時間大幅下降

在持續整合(CI)流程中,Docker 映像檔的建置速度往往成為影響交付效率與成本的重要因素。傳統建置方式若無法有效重用快取,每次建置都需重新下載相依套件與重跑重複步驟,容易導致 CI 執行時間隨專案成長而顯著拉長。BuildKit 是 Docker 提供的現代化建置引擎,透過更細緻的快取管理機制與建置階段控制,能有效提升快取命中率並減少不必要的重建行為。

透過搭配進階快取策略,例如將套件下載結果與編譯產物獨立快取,BuildKit 能在多次建置、不同分支或多個 CI 節點之間重用既有成果,使建置流程更具可預期性。這不僅能大幅縮短 CI 執行時間,也有助於降低基礎設施資源消耗,提升整體交付效率,特別適用於規模化專案與高頻率部署的生產環境。

在實務上,BuildKit 的價值並不僅止於加快單次建置速度,而在於讓建置流程具備「可預期的快取行為」。當相依套件、編譯步驟與應用程式內容被清楚分離並正確快取後,即使專案規模擴大或建置頻率提高,CI 流程仍能維持穩定且線性的執行時間,避免因重複建置而造成資源浪費。

因此,在設計 Dockerfile 與 CI 流程時,應將 BuildKit 視為建置架構的一部分,而非單純的效能優化工具。這樣的設計思維,能使映像檔建置流程更容易擴展、維護與標準化,特別適合用於多服務、多環境與長期維運的專案。
BuildKit 的核心價值在於提供穩定且可重用的快取機制,使映像檔建置流程在專案成長與部署頻率提高的情況下,仍能保持一致的效能表現。

若你在 CI 上 build 很慢,通常原因是:

  • 依賴層 cache 沒被有效保存
  • build 每次都從頭下載 dependencies

BuildKit cache mount(以 apt / npm 為例)

				
					# syntax=docker/dockerfile:1.7

RUN --mount=type=cache,target=/var/cache/apt \
    apt-get update && apt-get install -y --no-install-recommends curl

				
			

npm 也可以:

				
					RUN --mount=type=cache,target=/root/.npm \
    npm ci

				
			

這會顯著改善 CI build,尤其是你有多分支、多次部署的情境。

常見踩雷與排錯清單(最實用)

在實際導入 Docker 與容器化流程時,許多問題並非源自工具本身,而是來自 Dockerfile 設計、建置流程或執行環境假設不一致所累積的細微錯誤。這些問題往往在專案初期不易察覺,但隨著映像檔數量增加、CI/CD 頻率提高或正式環境上線後,便會逐漸放大其影響,導致建置時間過長、映像檔體積失控、部署不穩定,甚至增加安全風險。

本節整理實務中最常遇到的 Docker Image 與 Dockerfile 踩雷情境,並對應提供排查方向與修正思路,目的在於協助讀者快速辨識問題根源,避免反覆嘗試與非必要的重構成本。透過系統化地理解這些常見錯誤,能有效提升容器化流程的可預期性,並讓 Docker 在正式環境中發揮其應有的穩定性與效率。

容器化流程中的效能與穩定性問題,多半來自 Dockerfile 與建置流程的細節設計。本節聚焦於實務上最常見的踩雷情境,協助讀者快速定位問題並建立正確的排錯思維,以降低維運成本並提升部署可靠度。

問題 1:映像檔越來越大

檢查:

  • 有沒有 .dockerignore
  • 有沒有把 build 產物、測試資料 COPY 進去
  • 有沒有清 apt cache / npm cache
  • 是否缺少 multi-stage

問題 2:CI build 每次都重新跑依賴

檢查:

  • Dockerfile 的 COPY 順序是否正確(先 lock 檔後程式碼)
  • 有沒有啟用 BuildKit cache
  • registry 是否支援 layer cache

問題 3:容器在你機器跑,部署就掛

檢查:

  • 你是不是用了「依賴本機檔案」或「硬編碼路徑」
  • 是否缺少必要的 system lib(尤其 Python / Node native module)
  • base image distro 差異(alpine vs debian)

結論:你要把 Dockerfile 當成「可維運的產品規格」

在容器化架構中,Dockerfile 往往被視為單純的建置工具,用來將應用程式包裝成可執行的 Docker Image。然而,隨著系統規模擴大、部署頻率提高,以及正式環境對穩定性與安全性的要求提升,Dockerfile 的角色已不再只是「讓服務跑起來」,而是逐漸成為一份具備工程約束力的交付規格。

一份設計良好的 Dockerfile,實際上定義了應用程式在不同環境中的行為邊界,包括相依套件的來源、建置流程的可重現性、映像檔的組成結構,以及服務在容器中的執行方式。這些設計決策,將直接影響映像檔的體積、建置效率、部署穩定性與後續維運成本,也會在 CI/CD、自動化測試與安全稽核流程中被不斷放大其影響。

相反地,若 Dockerfile 缺乏結構與一致性,或僅為短期需求而快速堆疊,初期或許能快速完成部署,但隨著專案演進,往往會轉化為技術債,導致建置時間失控、環境行為不可預期,甚至在發生問題時難以追溯原因。這類風險並非來自 Docker 本身,而是來自未將 Dockerfile 視為正式工程產物所衍生的結果。

因此,在正式環境中,Dockerfile 應被視為與程式碼同等重要的規格文件,需要被設計、審視與持續維護。透過落實 Dockerfile 最佳實務,包括合理的分層策略、Multi-stage builds、映像檔精簡、安全原則與建置流程標準化,Docker Image 才能成為一個可長期維運、可審計、可複製的交付單位,支撐後續的容器編排、多服務架構與更高層次的基礎設施演進。

當 Dockerfile 被正確定位為可維運的產品規格,容器化不再只是部署技術,而會成為一套穩定、可預期且能隨業務成長而擴展的工程基礎。

如果你只把 Dockerfile 當「打包工具」,那你只是在做能跑的容器;
但如果你把 Dockerfile 當「可維運、可審計、可複製的交付規格」,你就會得到:

  • 更快的建置與部署(快取命中 + multi-stage)
  • 更小的映像檔(省成本、加速發佈)
  • 更低的安全風險(非 root、固定版本、減少攻擊面)
  • 更穩定的生產維運(healthcheck、可重現 build)

Q&A 常見問題 | Docker 常見重點

Dockerfile 是用來定義映像檔建置流程的規格文件,描述基底映像、相依套件、檔案複製方式與啟動行為;Docker Image 則是依據 Dockerfile 建立的最終成果,可被部署並實際執行於容器環境中。Dockerfile 決定 Image 的結構與內容,而 Image 則是可交付與可部署的單位。

Dockerfile 最佳實務指的是一套能兼顧建置效率、映像檔體積、安全性與可維運性的撰寫原則,包含正確的分層順序、提高快取命中率、使用 Multi-stage builds、精簡映像檔內容、避免以 root 權限執行服務,以及固定 base image 版本以確保建置可重現性。

Multi-stage builds 能將建置階段與執行階段明確分離,只將實際執行所需的產物納入最終映像檔,避免將編譯工具與開發相依一併打包。這不僅能大幅縮小映像檔體積,也能降低攻擊面,對正式環境的效能與安全性都有實質幫助。

映像檔體積失控,通常來自 Dockerfile 分層設計不當,例如缺少 .dockerignore、未清除套件快取、將建置產物或不必要的檔案一併 COPY 進映像檔,或未使用 Multi-stage builds。這類問題在專案初期不易察覺,但會隨著迭代逐漸累積成維運負擔。

以 root 權限執行容器,會在發生漏洞或入侵時放大影響範圍。透過使用非特權使用者執行服務,可有效落實最小權限原則,降低橫向移動與系統被濫用的風險,這已是多數正式環境與資安規範的基本要求。

任何在 Dockerfile 建置階段寫入的機密資訊,都會被永久保存在映像檔層中,並隨映像檔散佈至 Registry 或其他環境,形成嚴重的供應鏈風險。正式環境中,機密資訊應與映像檔分離,改由執行階段以安全機制注入。

會,而且影響非常直接。不良的 Dockerfile 設計會導致快取失效,使每次建置都重新下載相依套件與重跑步驟,顯著拉長 CI 執行時間。相反地,良好的分層與快取策略,能讓建置時間在專案成長後仍維持可預期的表現。

延伸閱讀

  1. Docker 是什麼?完整解析容器化核心概念、實務操作與常見問題(新手到實戰)
  2. Docker Image 與 Dockerfile 實戰教學(最佳實務與效能優化)
  3. Docker Compose 是什麼?多容器應用完整部署指南
  4. Docker Volume 與資料持久化完整解析(正式環境必讀)
  5. Docker Network 架構說明:容器如何安全互通?
  6. Docker 正式部署怎麼選主機?效能、穩定性與擴充性分析
  7. Docker 進階實務:安全性(Security)與映像檔最佳化完整指南

By taki

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *