Docker 進階與 Compose
Multi-stage Build(多階段建置)
💡 比喻:搬家 你在舊家(Build 階段)有很多工具、材料, 但搬到新家(Runtime 階段)只帶需要的家具就好。 Multi-stage Build 就是只把「成品」帶到最終的 Image, 不帶開發工具和原始碼,讓 Image 更小更安全。
.NET Multi-stage Dockerfile
# === 階段 1:Build(建置)===
# 使用完整的 SDK Image(包含編譯工具)
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
# 設定工作目錄
WORKDIR /src
# 先複製 csproj 檔案(利用 Docker 快取層)
COPY *.csproj ./
# 還原 NuGet 套件(這層很少變動,可以快取)
RUN dotnet restore
# 複製所有原始碼
COPY . .
# 發佈應用程式(Release 模式)
RUN dotnet publish -c Release -o /app/publish
# === 階段 2:Runtime(執行)===
# 使用精簡的 ASP.NET Runtime Image(沒有 SDK)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
# 設定工作目錄
WORKDIR /app
# 只從 build 階段複製發佈結果
COPY --from=build /app/publish .
# 設定容器對外的 port
EXPOSE 8080
# 設定啟動命令
ENTRYPOINT ["dotnet", "MyApp.dll"]
Image 大小比較
方式 Image 大小
────────────────────────────────────────
直接用 SDK Image ≈ 800 MB(包含編譯工具)
Multi-stage(aspnet) ≈ 220 MB(只有 Runtime)
Multi-stage(alpine) ≈ 110 MB(精簡 Linux)
alpine 版本更小,但可能缺少某些 Linux 套件
Docker Compose
💡 比喻:樂團指揮 你的應用程式像一個樂團:
- Web App = 主唱
- Database = 鼓手
- Redis Cache = 吉他手 Docker Compose 就是指揮,一個手勢讓所有人同時開始演奏。
docker-compose.yml 基本結構
# Docker Compose 設定檔版本
version: '3.8'
# 定義所有服務
services:
# 服務 1:Web 應用程式
web:
# 從當前目錄的 Dockerfile 建置
build: .
# port 對應:主機 5000 → 容器 8080
ports:
- "5000:8080"
# 環境變數
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__Default=Server=db;Database=myapp;User=sa;Password=YourPassword123!
# 相依性:web 會等 db 和 redis 啟動後才啟動
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
# 掛載磁碟區(開發時方便修改)
volumes:
- ./logs:/app/logs
# 連接到自訂網路
networks:
- app-network
# 健康檢查
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
# 服務 2:SQL Server 資料庫
db:
# 使用官方 SQL Server Image
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=YourPassword123!
ports:
- "1433:1433"
# 持久化資料(容器刪除後資料還在)
volumes:
- sql_data:/var/opt/mssql
networks:
- app-network
# 資料庫健康檢查
healthcheck:
test: ["CMD", "/opt/mssql-tools18/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", "YourPassword123!", "-Q", "SELECT 1", "-C"]
interval: 10s
timeout: 5s
retries: 5
# 服務 3:Redis 快取
redis:
# 使用官方 Redis Alpine Image(精簡版)
image: redis:alpine
ports:
- "6379:6379"
networks:
- app-network
# 定義持久化磁碟區
volumes:
sql_data:
# 定義網路
networks:
app-network:
driver: bridge
常用 Docker Compose 命令
# 建置並啟動所有服務(背景執行)
docker compose up -d --build
# 查看所有服務狀態
docker compose ps
# 查看特定服務的 Log
docker compose logs web
# 即時追蹤 Log(像 tail -f)
docker compose logs -f web
# 停止所有服務
docker compose stop
# 停止並刪除所有容器、網路
docker compose down
# 停止並刪除所有容器、網路、磁碟區(資料也會刪除!)
docker compose down -v
# 只重建並重啟 web 服務
docker compose up -d --build web
# 進入容器內部執行命令
docker compose exec web bash
環境變數管理
.env 檔案
# .env 檔案(不要推上 Git!記得加到 .gitignore)
# 資料庫密碼
SA_PASSWORD=YourPassword123!
# JWT 金鑰
JWT_KEY=your-super-secret-jwt-key
# 應用程式環境
ASPNETCORE_ENVIRONMENT=Development
# docker-compose.yml 中使用 .env 變數
services:
db:
environment:
# 用 ${} 引用 .env 中的變數
- SA_PASSWORD=${SA_PASSWORD}
web:
environment:
- JWT_KEY=${JWT_KEY}
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT}
Health Checks(健康檢查)
// ASP.NET Core 設定健康檢查端點
// Program.cs
// 加入健康檢查服務
builder.Services.AddHealthChecks()
// 檢查資料庫連線
.AddSqlServer(
builder.Configuration.GetConnectionString("Default")!,
name: "database")
// 檢查 Redis 連線
.AddRedis("localhost:6379", name: "redis");
// 設定健康檢查路由
app.MapHealthChecks("/health");
🤔 我這樣寫為什麼會錯?
❌ 錯誤 1:Image 太大(沒用 Multi-stage Build)
# ❌ 錯誤:用 SDK Image 來執行(超大!800MB)
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
COPY . .
RUN dotnet publish -c Release -o /out
# SDK 包含編譯器、NuGet 快取等,不需要出現在正式環境
ENTRYPOINT ["dotnet", "/out/MyApp.dll"]
# ✅ 正確:用 Multi-stage Build,最終 Image 只有 Runtime
# 參考上面的 Multi-stage Dockerfile 範例
# 最終 Image 只有 ~110-220 MB
❌ 錯誤 2:容器用 root 執行
# ❌ 錯誤:預設以 root 身份執行(安全風險)
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
# ✅ 正確:建立非 root 使用者來執行
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app/publish .
# 建立一個叫 appuser 的非 root 使用者
RUN adduser --disabled-password --gecos "" appuser
# 切換到 appuser 身份執行
USER appuser
ENTRYPOINT ["dotnet", "MyApp.dll"]
❌ 錯誤 3:把密碼寫在 docker-compose.yml 中
# ❌ 錯誤:密碼直接寫在設定檔中(推上 Git 就洩漏了)
services:
db:
environment:
- SA_PASSWORD=MyRealPassword123!
# ✅ 正確:用 .env 檔案或 Docker Secrets
# 1. 使用 .env(記得加入 .gitignore)
# 2. 使用 Docker Secrets(適合 Swarm 模式)
# 3. 使用 CI/CD 平台的環境變數功能
💡 重點整理
| 概念 | 說明 |
|---|---|
| Multi-stage Build | 分階段建置,最終 Image 只包含 Runtime |
| Docker Compose | 一次管理多個容器的工具 |
| depends_on | 定義服務啟動順序 |
| volumes | 持久化資料,容器刪除後資料還在 |
| networks | 容器間的虛擬網路 |
| Health Check | 定期檢查服務是否正常運作 |
| .env | 環境變數檔案,不要推上 Git |