目錄
Dockerfile 基本概念
Dockerfile 是什麼?
Dockerfile = 建立 Docker Image 的腳本
= 定義如何組裝 Image 的指令集
= 可以版本控制的基礎設施代碼
核心特點:
- 純文字檔案,易於版本控制
- 自動化建立 Image
- 可重複、可追蹤
- 標準化環境配置
Dockerfile 基本結構
# 1. 基礎 Image
FROM node:18-alpine
# 2. 維護者資訊(選擇性)
LABEL maintainer="dev@example.com"
# 3. 環境變數
ENV NODE_ENV=production
# 4. 工作目錄
WORKDIR /app
# 5. 複製檔案
COPY package*.json ./
RUN npm install
COPY . .
# 6. 暴露 Port
EXPOSE 3000
# 7. 啟動指令
CMD ["npm", "start"]
建立 Image
# 基本建立
docker build -t myapp .
# 指定 Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .
# 建立時帶參數
docker build --build-arg VERSION=1.0 -t myapp .
# 不使用快取
docker build --no-cache -t myapp .
Dockerfile 指令詳解
FROM - 基礎 Image
# 基本使用
FROM ubuntu:20.04
FROM node:18-alpine
FROM python:3.11-slim
# 多階段建立
FROM node:18 AS builder
FROM nginx:alpine AS runtime
# 從 scratch 開始(完全空白)
FROM scratch
說明:
- 每個 Dockerfile 必須以 FROM 開頭
- 定義基礎 Image
- 可以有多個 FROM(多階段建立)
scratch是特殊的空 Image
選擇建議:
優先順序:
1. alpine 版本(最小,~5-10MB)
2. slim 版本(較小,~50-100MB)
3. 標準版本(完整,~200-900MB)
範例:
node:18-alpine → 推薦
node:18-slim → 需要更多工具時
node:18 → 避免使用(除非必要)
LABEL - 標籤元資料
# 單個標籤
LABEL version="1.0"
LABEL description="My Application"
# 多個標籤
LABEL maintainer="dev@example.com" \
version="1.0" \
description="Production ready app"
說明:
- 為 Image 添加元資料
- 可以用於文件化、版本控制
- 查看:
docker inspect myapp
WORKDIR - 工作目錄
# 設定工作目錄
WORKDIR /app
# 相當於
RUN mkdir -p /app && cd /app
# 可以多次使用
WORKDIR /app
WORKDIR src # 現在在 /app/src
WORKDIR ../dist # 現在在 /app/dist
說明:
- 設定之後指令的工作目錄
- 如果目錄不存在會自動建立
- 影響 RUN、CMD、ENTRYPOINT、COPY、ADD
- 建議使用絕對路徑
COPY vs ADD
# COPY - 單純複製(推薦)
COPY package.json .
COPY src/ /app/src/
COPY app.js .
# ADD - 複製 + 額外功能
ADD archive.tar.gz /app/ # 自動解壓縮
ADD https://example.com/file.txt /app/ # 下載檔案
對比:
| 指令 | COPY | ADD |
|---|---|---|
| 複製檔案 | ✅ | ✅ |
| 複製目錄 | ✅ | ✅ |
| 自動解壓 | ❌ | ✅ (.tar.gz, .tar.xz) |
| 遠端 URL | ❌ | ✅ |
| 推薦使用 | ✅ | 特殊情況 |
最佳實踐:
# ✅ 推薦:使用 COPY
COPY requirements.txt .
# ❌ 避免:不必要的 ADD
ADD requirements.txt .
# ✅ 特殊情況:需要解壓縮
ADD archive.tar.gz /app/
RUN - 執行指令
# Shell 形式(/bin/sh -c)
RUN apt-get update
RUN npm install
# Exec 形式(推薦,不經過 shell)
RUN ["npm", "install"]
RUN ["/bin/bash", "-c", "echo hello"]
# 鏈結指令(減少層數)
RUN apt-get update && \
apt-get install -y \
curl \
vim \
git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
說明:
- 在建立 Image 時執行
- 每個 RUN 會建立新的層
- 合併指令可減少層數
- Exec 形式不會展開變數
減少層數技巧:
# ❌ 不好:4 層
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN apt-get clean
# ✅ 好:1 層
RUN apt-get update && \
apt-get install -y curl vim && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
CMD vs ENTRYPOINT
CMD - 預設指令
# Shell 形式
CMD npm start
# Exec 形式(推薦)
CMD ["npm", "start"]
CMD ["node", "server.js"]
# 作為 ENTRYPOINT 的參數
CMD ["--port", "3000"]
ENTRYPOINT - 入口點
# Exec 形式(推薦)
ENTRYPOINT ["docker-entrypoint.sh"]
ENTRYPOINT ["node"]
# Shell 形式
ENTRYPOINT node server.js
組合使用
# 範例 1:彈性參數
ENTRYPOINT ["node"]
CMD ["app.js"]
# 執行
docker run myapp # → node app.js
docker run myapp server.js # → node server.js
# 範例 2:固定指令 + 彈性參數
ENTRYPOINT ["mysql"]
CMD ["--default-authentication-plugin=mysql_native_password"]
# 執行
docker run mydb # → mysql --default-authentication-plugin=...
docker run mydb --port 3307 # → mysql --port 3307
使用場景:
| 情況 | 使用 | 範例 |
|---|---|---|
| 提供預設指令 | CMD | CMD ["npm", "start"] |
| 定義容器主要功能 | ENTRYPOINT | ENTRYPOINT ["nginx"] |
| 指令 + 彈性參數 | 兩者組合 | ENTRYPOINT ["node"] + CMD ["app.js"] |
ENV - 環境變數
# 單個變數
ENV NODE_ENV=production
ENV PORT=3000
# 多個變數
ENV NODE_ENV=production \
PORT=3000 \
DB_HOST=localhost
# 在指令中使用
ENV APP_HOME=/app
WORKDIR $APP_HOME
COPY . $APP_HOME
說明:
- 設定環境變數
- 在 Container 中可以使用
- 可以在
docker run時用-e覆蓋 - 影響所有後續指令
使用時機:
# ✅ 好:可能需要修改的配置
ENV NODE_ENV=production
ENV MAX_MEMORY=512M
# ❌ 不好:敏感資訊(使用 secrets)
ENV DB_PASSWORD=secret123 # 不要這樣做!
ARG - 建立參數
# 定義參數
ARG VERSION=1.0
ARG NODE_VERSION=18
# 使用參數
FROM node:${NODE_VERSION}-alpine
RUN echo "Building version ${VERSION}"
# 有預設值
ARG ENVIRONMENT=development
建立時傳入:
docker build --build-arg VERSION=2.0 --build-arg NODE_VERSION=20 -t myapp .
ARG vs ENV:
| 特性 | ARG | ENV |
|---|---|---|
| 生命週期 | 建立時 | 建立時 + 執行時 |
| 可見性 | Dockerfile | Container |
| 用途 | 建立參數 | 環境配置 |
EXPOSE - 聲明 Port
# 聲明 Port
EXPOSE 3000
EXPOSE 8080/tcp
EXPOSE 53/udp
# 多個 Port
EXPOSE 80 443
說明:
- 文件化作用(告知使用者)
- 不會實際開啟 Port
- 需要
-p或-P才能映射
# 仍需要 -p 映射
docker run -p 8080:3000 myapp
VOLUME - 掛載點
# 單個 Volume
VOLUME /data
# 多個 Volume
VOLUME ["/var/log", "/var/db"]
# 使用變數
ENV DATA_DIR=/app/data
VOLUME $DATA_DIR
說明:
- 定義掛載點
- 資料持久化
- 可以在
docker run時用-v指定
USER - 切換使用者
# 創建使用者
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 切換使用者
USER appuser
# 之後的指令都以 appuser 執行
WORKDIR /app
CMD ["npm", "start"]
安全最佳實踐:
# ❌ 不好:使用 root
FROM node:18-alpine
WORKDIR /app
COPY . .
CMD ["npm", "start"] # 以 root 執行
# ✅ 好:使用非 root 使用者
FROM node:18-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY . .
USER appuser
CMD ["npm", "start"] # 以 appuser 執行
HEALTHCHECK - 健康檢查
# 基本健康檢查
HEALTHCHECK \
CMD curl -f http://localhost/ || exit 1
# HTTP 檢查
HEALTHCHECK \
CMD curl -f http://localhost:3000/health || exit 1
# 自訂腳本
HEALTHCHECK CMD /app/healthcheck.sh
# 禁用健康檢查
HEALTHCHECK NONE
參數說明:
--interval=30s:每 30 秒檢查一次--timeout=3s:超過 3 秒視為失敗--retries=3:連續失敗 3 次視為 unhealthy--start-period=60s:啟動後 60 秒內不算失敗
Dockerfile 最佳實踐
1. 多階段建立(Multi-stage Build)
# 第一階段:建立
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 第二階段:執行
FROM node:18-alpine
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/main.js"]
優點:
✅ 最終 Image 更小(只包含執行時需要的檔案)
✅ 不包含建立工具和中間檔案
✅ 更安全(減少攻擊面)
✅ 建立環境和執行環境分離
實際效果對比:
單階段:1.2GB
多階段:150MB
減少:87.5%
2. 利用快取機制
# ❌ 不好:每次程式碼改變都重裝依賴
COPY . .
RUN npm install
# ✅ 好:利用快取
COPY package*.json ./
RUN npm install # ← 只有 package.json 改變才重新執行
COPY . . # ← 程式碼改變不影響上面的快取
快取優化策略:
# 1. 先複製依賴檔案
COPY package.json package-lock.json ./
# 2. 安裝依賴(快取)
RUN npm ci
# 3. 複製其他檔案
COPY src/ ./src/
COPY public/ ./public/
COPY tsconfig.json ./
# 4. 建立
RUN npm run build
3. 減少層數
# ❌ 不好:太多層(4 層)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN apt-get clean
# ✅ 好:合併指令(1 層)
RUN apt-get update && \
apt-get install -y \
curl \
vim && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
層數優化原則:
- 合併相關的 RUN 指令
- 每個 RUN 結束時清理暫存檔案
- 保持可讀性和可維護性的平衡
4. 使用 .dockerignore
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
*.md
*.log
dist
build
coverage
.vscode
.idea
# 保留特定檔案
!README.production.md
作用:
✅ 減少建立 context 大小
✅ 加快建立速度
✅ 避免複製不需要的檔案
✅ 避免將敏感資訊複製到 Image
5. 選擇適當的基礎 Image
# 大小對比
FROM node:18 # ~900MB
FROM node:18-slim # ~200MB
FROM node:18-alpine # ~100MB
# 建議選擇
FROM node:18-alpine # ✅ 優先使用
# 需要特定工具時
FROM node:18-slim # ✅ 次選
# 避免
FROM node:18 # ❌ 除非必要
Alpine 注意事項:
# Alpine 使用 musl libc,可能與 glibc 不相容
# 需要編譯原生模組時可能遇到問題
# 解決方案 1:安裝建立工具
RUN apk add --no-cache python3 make g++
# 解決方案 2:使用 slim 版本
FROM node:18-slim
6. 最小化層的大小
# ❌ 錯誤:分開刪除不會減少大小
RUN wget https://example.com/bigfile.tar.gz # +500MB
RUN tar xzf bigfile.tar.gz # +50MB
RUN rm bigfile.tar.gz # +0MB (bigfile 仍在上一層)
# 總大小:550MB
# ✅ 正確:在同一層刪除
RUN wget https://example.com/bigfile.tar.gz && \
tar xzf bigfile.tar.gz && \
rm bigfile.tar.gz
# 總大小:50MB
7. 使用非 root 使用者
# ✅ 最佳實踐
FROM node:18-alpine
# 創建使用者
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup
# 設定權限
WORKDIR /app
COPY package*.json ./
RUN npm ci && npm cache clean --force
COPY . .
# 切換使用者
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]
8. 標籤和版本管理
# ❌ 不好:使用 latest
docker build -t myapp:latest .
# ✅ 好:使用語義化版本
docker build -t myapp:1.2.3 .
docker build -t myapp:1.2 .
docker build -t myapp:1 .
# ✅ 使用 Git 提交
docker build -t myapp:$(git rev-parse --short HEAD) .
# ✅ 使用時間戳
docker build -t myapp:$(date +%Y%m%d-%H%M%S) .
多階段建立
Node.js 應用範例
# ============================================
# Stage 1: 依賴安裝
# ============================================
FROM node:18-alpine AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
# ============================================
# Stage 2: 建立
# ============================================
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ============================================
# Stage 3: 執行
# ============================================
FROM node:18-alpine AS runtime
# 安全性:創建非 root 使用者
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup
WORKDIR /app
# 只複製必要的檔案
COPY /app/node_modules ./node_modules
COPY /app/dist ./dist
COPY package*.json ./
# 切換使用者
USER appuser
# 健康檢查
HEALTHCHECK \
CMD node healthcheck.js || exit 1
EXPOSE 3000
CMD ["node", "dist/main.js"]
大小對比:
所有階段:~1.5GB
最終 Image:~150MB
減少:90%
Go 應用範例
# ============================================
# Stage 1: 建立
# ============================================
FROM golang:1.21-alpine AS builder
WORKDIR /app
# 複製依賴檔案
COPY go.mod go.sum ./
RUN go mod download
# 複製程式碼
COPY . .
# 建立(靜態編譯)
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# ============================================
# Stage 2: 執行
# ============================================
FROM scratch
# 複製 CA 憑證(HTTPS 需要)
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# 複製執行檔
COPY /app/main /main
EXPOSE 8080
CMD ["/main"]
大小對比:
使用 golang 基礎:~800MB
使用 scratch:~10MB
減少:98.75%
Java 應用範例
# ============================================
# Stage 1: 建立
# ============================================
FROM gradle:8-jdk17 AS builder
WORKDIR /app
# 複製依賴檔案(利用快取)
COPY build.gradle settings.gradle ./
COPY gradle ./gradle
RUN gradle dependencies --no-daemon
# 複製程式碼並建立
COPY src ./src
RUN gradle build --no-daemon
# ============================================
# Stage 2: 執行
# ============================================
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 創建非 root 使用者
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup
# 複製 JAR 檔案
COPY /app/build/libs/*.jar app.jar
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
實戰範例
範例 1:Python Flask 應用
FROM python:3.11-slim
# 安全性:使用非 root 使用者
RUN useradd -m -u 1000 appuser
WORKDIR /app
# 複製依賴檔案
COPY requirements.txt .
# 安裝依賴
RUN pip install --no-cache-dir -r requirements.txt
# 複製應用程式
COPY . .
# 切換使用者
USER appuser
EXPOSE 5000
# 健康檢查
HEALTHCHECK \
CMD python healthcheck.py || exit 1
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
範例 2:Nginx 靜態網站
# ============================================
# Stage 1: 建立
# ============================================
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ============================================
# Stage 2: 執行
# ============================================
FROM nginx:alpine
# 複製建立的靜態檔案
COPY /app/dist /usr/share/nginx/html
# 複製 Nginx 配置
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
常見問題
問題 1:Build Context 太大
# 錯誤訊息
Sending build context to Docker daemon 2.5GB
# 解決方案:使用 .dockerignore
echo "node_modules" >> .dockerignore
echo ".git" >> .dockerignore
echo "*.log" >> .dockerignore
問題 2:快取沒有生效
# ❌ 問題:COPY . . 會讓快取失效
COPY . .
RUN npm install
# ✅ 解決:先複製 package.json
COPY package*.json ./
RUN npm install
COPY . .
問題 3:Image 太大
# 檢查 Image 大小
docker images myapp
# 查看各層大小
docker history myapp
# 優化方案
# 1. 使用 alpine 基礎
# 2. 多階段建立
# 3. 清理暫存檔案
# 4. 合併 RUN 指令
進階技巧
使用 BuildKit
# 啟用 BuildKit(更快、更好的快取)
export DOCKER_BUILDKIT=1
docker build -t myapp .
# 或在指令前加上
DOCKER_BUILDKIT=1 docker build -t myapp .
條件性 COPY
# 使用 ARG 控制
ARG ENVIRONMENT=production
COPY package*.json ./
RUN npm ci
# 只在開發環境複製測試檔案
COPY . .
RUN if [ "$ENVIRONMENT" = "development" ]; then \
npm install --only=dev; \
fi
平行建立
# 使用多個 FROM 平行建立
FROM base AS deps
RUN install-deps
FROM base AS test
RUN run-tests
FROM base AS build
COPY /deps .
RUN build-app
總結
核心要點
- Dockerfile 是建立 Docker Image 的腳本,可版本控制
- 使用多階段建構可大幅減少最終 Image 大小
- 利用快取機制加速建構:先複製依賴檔案
- 合併 RUN 指令、在同一層清理暫存檔案減少層數
- 使用 alpine 基礎 Image、非 root 使用者提高安全性
快速參考
| 指令 | 作用 | 最佳實踐 |
|---|---|---|
| FROM | 基礎 Image | 優先 alpine |
| RUN | 執行指令 | 合併指令 |
| COPY | 複製檔案 | 先複製依賴 |
| CMD | 預設指令 | 用 Exec 形式 |
| EXPOSE | 聲明 Port | 文件化 |
| USER | 切換使用者 | 避免 root |
建立日期:2025-11-10 最後更新:2025-11-18