dockerfile

什麼是 dockerfile

引言

在 Docker 的世界裡,Dockerfile 是一個關鍵的組件。它定義了如何構建一個 Docker 映像(image),這個映像可以被用來創建容器(container)。理解 Dockerfile 的結構和語法是使用 Docker 的基礎。

Dockerfile 基礎結構

其實可以這樣概括 Dockerfile 的結構:

  1. 基礎環境:FROM 指令
  2. 元資訊:LABEL 指令(原本是 MAINTAINER
  3. 建構過程:RUNCOPYENVWORKDIR 等指令
  4. 啟動行為:CMDENTRYPOINTUSER 等指令

實際應用

這裡是一個說明完整的 Dockerfile 範例,適合用來構建一個簡單的 Node.js Web 應用:

# 1. 基礎鏡像:使用官方 Node.js Alpine 版本(輕量級)
FROM node:18-alpine

# 2. 元資料:標記作者資訊
LABEL maintainer="yourname@example.com"

# 3. 設定環境變數,方便在應用內讀取(非必須,但常見)
ENV NODE_ENV=production

# 4. 設定工作目錄(容器內的執行目錄)
WORKDIR /app

# 5. 複製 package.json 和 package-lock.json 到容器中
COPY package*.json ./

# 6. 安裝依賴(建議用 --frozen-lockfile 或 --production)
RUN npm install --production

# 7. 複製整個專案到容器中(除了 .dockerignore 排除的檔案)
COPY . .

# 8. 暴露容器內部的 port(此行為只是文檔標示,不會真正開 port)
EXPOSE 3000

# 9. 指定預設啟動命令
CMD ["node", "index.js"]

問題分析及解釋

針對上面的 Dockerfile 範例,有些常見疑慮,提出幾個來說明:

ENV 設定環境變數的意義:讓容器更有彈性與可維護性

ENV NODE_ENV=production

這行是設定容器內部的環境變數。設定後,容器中任何程式都可以透過 process.env.NODE_ENV(Node.js)或 $NODE_ENV(Shell)來存取。

這讓我們能根據環境(「開發環境」還是「正式環境」)自動調整行為,例如日誌等級、是否要開啟除錯模式、使用正式資料庫、載入不同設定檔等等。

使用 ENV 的好處不只是讓映像更通用,它同時也是一種環境隔離的策略,避免我們硬編設定值在應用裡,只改變變數就好。如果搭配 .env 檔和 CI/CD 流程,還能進一步做到安全地注入敏感資訊,例如金鑰或資料庫密碼,達到真正的「一次打包,到處運行」。

為什麼要先 copy package.json 再安裝依賴

COPY package*.json ./
RUN npm install
COPY . .

這樣的順序並不是隨便排的,而是為了善用 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 時能看到有哪些埠號是「設計上對外開放的」(給我們人操作時看的)。