引言
在 Docker 的世界裡,Dockerfile
是一個關鍵的組件。它定義了如何構建一個 Docker 映像(image),這個映像可以被用來創建容器(container)。理解 Dockerfile
的結構和語法是使用 Docker 的基礎。
Dockerfile 基礎結構
其實可以這樣概括 Dockerfile
的結構:
- 基礎環境:
FROM
指令 - 元資訊:
LABEL
指令(原本是MAINTAINER
) - 建構過程:
RUN
、COPY
、ENV
、WORKDIR
等指令 - 啟動行為:
CMD
、ENTRYPOINT
、USER
等指令
實際應用
這裡是一個說明完整的 Dockerfile
範例,適合用來構建一個簡單的 Node.js Web 應用:
問題分析及解釋
針對上面的 Dockerfile
範例,有些常見疑慮,提出幾個來說明:
ENV 設定環境變數的意義:讓容器更有彈性與可維護性
ENV NODE_ENV=production
這行是設定容器內部的環境變數。設定後,容器中任何程式都可以透過 process.env.NODE_ENV
(Node.js)或 $NODE_ENV
(Shell)來存取。
這讓我們能根據環境(「開發環境」還是「正式環境」)自動調整行為,例如日誌等級、是否要開啟除錯模式、使用正式資料庫、載入不同設定檔等等。
使用 ENV
的好處不只是讓映像更通用,它同時也是一種環境隔離的策略,避免我們硬編設定值在應用裡,只改變變數就好。如果搭配 .env
檔和 CI/CD 流程,還能進一步做到安全地注入敏感資訊,例如金鑰或資料庫密碼,達到真正的「一次打包,到處運行」。
為什麼要先 copy package.json 再安裝依賴
這樣的順序並不是隨便排的,而是為了善用 Docker 的建構快取(Layer Cache)。
Docker 在建構映像時,會為每一個指令產生一層(Layer):只要某一層的內容沒變動,Docker 就會直接快取,跳過該層的重建。這意味著,如果我們的 package.json
沒變動,npm install
這一層就能被快取住,不需要每次都重新下載依賴,大大節省建構時間。
但如果你直接把整個專案 COPY . .
往前擺,就算只是改了一個 .js
檔案,快取也會失效,導致 npm install
重跑。因此,先單獨複製 package.json
是一種非常實用的優化策略,如此一來可以讓 Docker build 快又有效率,也幾乎成為 Docker 社群中的最佳實踐。
EXPOSE 是什麼?為什麼說它「不會實際開 port」?
EXPOSE 3000
它只是宣告用途,告訴 Docker:「我這個容器預期會使用 3000
port 提供服務」。但要注意的是,EXPOSE
並不會真的幫你開 port 或把它連到主機上,只有在你啟動容器時要顯式綁定埠才會開。它只是個元資料(Metadata),像是在告訴其他開發者或工具:這個容器的 Web 服務是在 3000
port 上。
如果你真的要讓主機能連到這個 port,就得在 docker run
的時候用 -p
來明確對應,例如:
docker run -p 8080:3000 my-image
這樣才會把主機的 8080
port 映射到容器內的 3000
port。
EXPOSE
的用處主要是對其他 Docker 工具(如 Compose、Swarm、Kubernetes 等)提供預設值參考,或讓 docker inspect
時能看到有哪些埠號是「設計上對外開放的」(給我們人操作時看的)。