Dockerfile 實戰:容器化你的應用
在上一章中,我們安裝了 Docker 並理解了容器化的基本概念。現在,讓我們實際動手,將一個真實的應用程式打包成 Docker Image!
什麼是 Dockerfile?
Dockerfile 是一個純文字腳本檔案,裡面包含了一系列的「指令 (Instructions)」,告訴 Docker Engine 該如何逐步建立一個 Image。
你可以把 Dockerfile 想像成「食譜」:
- 食譜告訴你要準備哪些食材(基底 Image)
- 食譜告訴你料理的步驟(依序執行的指令)
- 最後產出成品(Docker Image)
Dockerfile 常用指令一覽
在開始寫 Dockerfile 之前,我們先快速了解幾個最重要的指令:
| 指令 | 功能 | 範例 |
|------|------|------|
| FROM | 指定基底 Image(必填) | FROM node:20-alpine |
| WORKDIR | 設定工作目錄 | WORKDIR /app |
| COPY | 複製檔案到 Image 中 | COPY package.json ./ |
| RUN | 在建置過程中執行指令 | RUN npm ci |
| ENV | 設定環境變數 | ENV NODE_ENV=production |
| EXPOSE | 宣告容器要暴露的埠 | EXPOSE 3000 |
| CMD | 容器啟動時預設執行的指令 | CMD ["node", "server.js"] |
| ENTRYPOINT | 容器啟動時的進入點(與 CMD 不同) | ENTRYPOINT ["node"] |
| HEALTHCHECK | 定義健康檢查指令 | HEALTHCHECK CMD curl ... |
| USER | 指定執行使用者 | USER node |
實戰:容器化一個 Next.js 應用
我們以一個常見的 Next.js 應用為例,從零開始撰寫 Dockerfile。
專案結構
my-next-app/
├── package.json
├── next.config.js
├── public/
├── src/
│ ├── app/
│ └── components/
└── Dockerfile ← 我們要新增的檔案
第一步:基礎版本(初階 Dockerfile)
在專案根目錄建立一個 Dockerfile(沒有副檔名):
# 使用 Node.js 20 作為基底 Image
FROM node:20
# 設定容器內的工作目錄
WORKDIR /app
# 複製套件描述檔
COPY package.json package-lock.json ./
# 安裝套件
RUN npm install
# 複製所有原始碼
COPY . .
# 建置應用
RUN npm run build
# 暴露埠號
EXPOSE 3000
# 啟動應用
CMD ["npm", "start"]
這個 Dockerfile 看似沒問題,但其實存在嚴重的效能與安全性問題。讓我們一一來看:
問題 1:使用完整的 node:20 作為基底
node:20 的體積約為 1.1 GB。但我們的應用在執行階段其實不需要 npm、編譯工具或系統套件。我們應該使用更輕量的 node:20-alpine(約 120 MB)或使用多階段建置。
問題 2:先 COPY 原始碼再安裝套件
這會導致 Docker 的快取機制失效。每當你修改了任何原始碼,COPY . . 這層的快取就會失效,迫使 Docker 重新執行 npm install。正確做法是先複製描述檔,安裝套件,再複製原始碼。
問題 3:使用 root 使用者執行 預設情況下,容器會以 root 身份執行。這違反了安全最佳實務(最小權限原則),如果攻擊者突破了應用,就可以直接控制容器。
第二步:優化版本(中階 Dockerfile)
FROM node:20-alpine
WORKDIR /app
# 先複製 package.json,利用 Docker 快取機制
COPY package.json package-lock.json ./
# 安裝正式環境套件(不含 devDependencies)
RUN npm ci --only=production
# 複製原始碼(這層會因為原始碼變動而失效,但 npm install 已快取)
COPY . .
# 建置
RUN npm run build
# 使用非 root 使用者
USER node
EXPOSE 3000
CMD ["npm", "start"]
這個版本已經解決了快取與安全性問題,但 Image 體積仍然偏大(約 400 MB),因為最終 Image 包含了建置工具與原始碼。
第三步:多階段建置(進階 Dockerfile)
多階段建置 (Multi-stage Build) 是 Docker 最強大的最佳化技巧之一。它的概念是:
- 第一階段 (Builder):使用完整的 Image,安裝全部工具,進行編譯與建置
- 第二階段 (Runner):使用最精簡的 Image,只從第一階段複製建置成果
最終的 Image 只包含「運行所需的最小檔案」,完全不包含編譯器、npm 套件或原始碼。
# === 第一階段:建置階段 ===
FROM node:20-alpine AS builder
WORKDIR /app
# 複製描述檔並安裝全部套件(包含 devDependencies)
COPY package.json package-lock.json ./
RUN npm ci
# 複製原始碼並建置
COPY . .
RUN npm run build
# === 第二階段:執行階段 ===
FROM node:20-alpine AS runner
WORKDIR /app
# 設定正式環境
ENV NODE_ENV=production
# 只複製必要的生產依賴
COPY package.json package-lock.json ./
RUN npm ci --only=production
# 從 builder 階段複製建置成果
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.js ./
# 使用非 root 使用者
USER node
EXPOSE 3000
# 健康檢查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
CMD ["npm", "start"]
多階段建置的優勢:
- ✅ Image 體積大幅縮小:從 1.1 GB → 約 150 MB
- ✅ 安全性提升:執行階段不包含原始碼或建置工具
- ✅ 部署速度加快:Image 越小,上傳下載越快
- ✅ 快取效率最大化:每個階段獨立快取
建置 Docker Image
Dockerfile 寫好之後,我們可以用 docker build 指令來建立 Image:
# 在專案根目錄執行(Dockerfile 所在的目錄)
docker build -t my-next-app:latest .
指令解析
docker build:建置 Image-t my-next-app:latest:指定 Image 的名稱 (tag),格式為名稱:版本.:指定 Dockerfile 所在的上下文路徑 (Build Context)
建置過程中,你會看到 Docker 一步一步地執行 Dockerfile 中的指令。第一次建置可能需要數分鐘,因為需要下載基底 Image 並安裝套件。
驗證 Image
建置完成後,用以下指令查看所有本機上的 Image:
docker images
輸出範例:
REPOSITORY TAG IMAGE ID CREATED SIZE
my-next-app latest a1b2c3d4e5f6 2 minutes ago 152MB
node 20-alpine 123abc456def 2 weeks ago 124MB
啟動容器
Image 建立好之後,就可以用 docker run 來啟動容器:
# 啟動容器,將主機的 3000 埠映射到容器的 3000 埠
docker run -d -p 3000:3000 --name my-app my-next-app:latest
指令解析
-d:背景執行 (detached)-p 3000:3000:連接埠映射 (主機埠:容器埠)--name my-app:指定容器名稱my-next-app:latest:使用的 Image
啟動後,開啟瀏覽器連到 http://localhost:3000,你應該可以看到應用正在運行!
[!TIP] 如果你啟動容器後發現無法連線,請確認你的應用監聽的埠號是否與 EXPOSE 相符。如果應用預設監聽 3000 但 Dockerfile 寫 EXPOSE 8080,就會出現埠號不一致的問題。
實用容器管理指令
# 查看運行中的容器
docker ps
# 查看所有容器(包含已停止的)
docker ps -a
# 查看容器日誌
docker logs my-app
# 即時追蹤日誌
docker logs -f my-app
# 進入容器內部(除錯用)
docker exec -it my-app sh
# 停止容器
docker stop my-app
# 啟動已停止的容器
docker start my-app
# 刪除容器
docker rm my-app
本日總結
在本章中,你學到了:
- ✅ Dockerfile 基本指令:FROM、WORKDIR、COPY、RUN、CMD 等核心語法
- ✅ 三種 Dockerfile 寫法:從基礎版到多階段建置的進化過程
- ✅ 快取機制:如何安排指令順序以最大化 Docker 快取效益
- ✅ 多階段建置:將 Image 體積從 1.1 GB 縮小到 150 MB
- ✅ Image 建置與容器啟動:使用 docker build 與 docker run
- ✅ 容器管理:查看日誌、進入容器、停止與刪除
下一章,我們將學習 Docker Compose——一次啟動多個相互關聯的容器!