☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
Docker 進階

雲端部署實戰

部署選項比較

平台              難度    價格        適合場景
──────────────────────────────────────────────────
Railway           低      免費方案    個人專案、學習用(我們實際在用!)
Azure App Service 中      免費方案    .NET 專案、企業級
AWS EC2           高      按用量      完全自訂、大規模
Heroku            低      免費方案    快速原型
DigitalOcean      中      固定月費    中小型專案

Railway 部署(我們實際用的!)

💡 Railway 是一個簡單的雲端平台,非常適合學習和小型專案。 支援從 GitHub 自動部署,幾乎不用設定就能上線。

部署步驟

1. 到 railway.app 註冊帳號(用 GitHub 登入)
2. 點「New Project」
3. 選「Deploy from GitHub repo」
4. 選擇你的 Repository
5. Railway 自動偵測 Dockerfile 或 .NET 專案
6. 設定環境變數
7. 部署完成!自動產生 URL

Railway 環境變數設定

在 Railway Dashboard 設定:

變數名稱                              值
─────────────────────────────────────────────────────
ASPNETCORE_ENVIRONMENT               Production
ConnectionStrings__Default           Server=...;Database=...
JWT_KEY                              your-production-jwt-key
PORT                                 8080(Railway 自動設定)

Railway 用的 Dockerfile

# 階段 1:建置
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# 複製專案檔案並還原套件
COPY *.csproj ./
RUN dotnet restore
# 複製所有原始碼並發佈
COPY . .
RUN dotnet publish -c Release -o /app/publish

# 階段 2:執行
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
# 從建置階段複製成品
COPY --from=build /app/publish .
# Railway 使用 PORT 環境變數
ENV ASPNETCORE_URLS=http://+:${PORT:-8080}
# 啟動應用程式
ENTRYPOINT ["dotnet", "MyApp.dll"]

Azure App Service 部署

使用 Azure CLI 部署

# 安裝 Azure CLI 後登入
az login

# 建立 Resource Group(資源群組)
az group create --name myapp-rg --location eastasia

# 建立 App Service Plan(選擇免費方案)
az appservice plan create \
  --name myapp-plan \
  --resource-group myapp-rg \
  --sku F1 \
  --is-linux

# 建立 Web App
az webapp create \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --plan myapp-plan \
  --runtime "DOTNETCORE:8.0"

# 設定環境變數
az webapp config appsettings set \
  --name myapp-unique-name \
  --resource-group myapp-rg \
  --settings ASPNETCORE_ENVIRONMENT=Production

# 部署程式碼
az webapp up \
  --name myapp-unique-name \
  --resource-group myapp-rg

GitHub Actions 部署到 Azure

# .github/workflows/azure-deploy.yml
name: Deploy to Azure

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      # 拉取程式碼
      - uses: actions/checkout@v4

      # 安裝 .NET
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      # 建置和發佈
      - name: Build and Publish
        run: dotnet publish -c Release -o ./publish

      # 部署到 Azure App Service
      - name: Deploy to Azure
        uses: azure/webapps-deploy@v2
        with:
          # App Service 的名稱
          app-name: myapp-unique-name
          # 從 GitHub Secrets 讀取發佈設定
          publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}
          # 指定發佈目錄
          package: ./publish

反向代理(Reverse Proxy)

💡 比喻:公司總機 客戶打電話到公司總機(反向代理), 總機根據需求轉接到不同部門(後端伺服器)。 客戶不需要知道每個部門的分機號碼。

Nginx 基本設定

# /etc/nginx/sites-available/myapp
# Nginx 反向代理設定
server {
    # 監聽 80 port(HTTP)
    listen 80;
    # 網域名稱
    server_name myapp.example.com;

    # 轉址到 HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    # 監聽 443 port(HTTPS)
    listen 443 ssl;
    server_name myapp.example.com;

    # SSL 憑證路徑
    ssl_certificate /etc/letsencrypt/live/myapp.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem;

    # 把所有請求轉發到 .NET 應用程式
    location / {
        # 轉發到本機的 5000 port(Kestrel)
        proxy_pass http://localhost:5000;
        # 傳遞原始的 Host 標頭
        proxy_set_header Host $host;
        # 傳遞使用者的真實 IP
        proxy_set_header X-Real-IP $remote_addr;
        # 傳遞轉發鏈的 IP
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # 告訴後端使用了 HTTPS
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Docker Compose 中使用 Nginx

# docker-compose.yml 中加入 Nginx
services:
  # Nginx 反向代理
  nginx:
    image: nginx:alpine
    ports:
      # 對外只開 80 和 443
      - "80:80"
      - "443:443"
    volumes:
      # 掛載 Nginx 設定檔
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      # 掛載 SSL 憑證
      - ./certs:/etc/nginx/certs
    depends_on:
      - web

  # .NET 應用程式(不直接對外)
  web:
    build: .
    # 不需要對外開 port,Nginx 會轉發
    expose:
      - "8080"

SSL/TLS 設定

Let's Encrypt 免費 SSL 憑證

# 安裝 Certbot(Let's Encrypt 的工具)
sudo apt install certbot python3-certbot-nginx

# 自動取得和設定 SSL 憑證
sudo certbot --nginx -d myapp.example.com

# 憑證自動更新(Let's Encrypt 憑證 90 天過期)
sudo certbot renew --dry-run

# 設定自動更新排程(每天檢查一次)
# sudo crontab -e
# 0 0 * * * certbot renew --quiet

監控與 Logging

Serilog 結構化日誌

// 安裝套件:
// dotnet add package Serilog.AspNetCore
// dotnet add package Serilog.Sinks.Console
// dotnet add package Serilog.Sinks.File

// Program.cs 設定 Serilog
using Serilog;

// 建立 Serilog Logger
Log.Logger = new LoggerConfiguration()
    // 最低日誌等級
    .MinimumLevel.Information()
    // 輸出到 Console(結構化格式)
    .WriteTo.Console()
    // 輸出到檔案(每天一個新檔案)
    .WriteTo.File(
        "logs/myapp-.log",
        rollingInterval: RollingInterval.Day,  // 每天產生新檔案
        retainedFileCountLimit: 30              // 保留 30 天
    )
    .CreateLogger();

// 在 ASP.NET Core 中使用 Serilog
builder.Host.UseSerilog();

// 在程式碼中記錄日誌
app.MapGet("/api/users/{id}", (int id, ILogger<Program> logger) =>
{
    // 結構化日誌:用 {} 包裹參數名稱
    logger.LogInformation("查詢使用者 {UserId}", id);

    try
    {
        // 處理邏輯...
        return Results.Ok();
    }
    catch (Exception ex)
    {
        // 記錄錯誤日誌(包含例外堆疊)
        logger.LogError(ex, "查詢使用者 {UserId} 時發生錯誤", id);
        return Results.StatusCode(500);
    }
});

Health Endpoint(健康檢查端點)

// Program.cs 設定完整的健康檢查
builder.Services.AddHealthChecks()
    // 檢查資料庫
    .AddSqlServer(
        builder.Configuration.GetConnectionString("Default")!,
        name: "database",
        timeout: TimeSpan.FromSeconds(5))
    // 自訂健康檢查
    .AddCheck("disk_space", () =>
    {
        // 檢查磁碟空間
        var drive = new DriveInfo("C");
        var freeSpacePercent = (double)drive.AvailableFreeSpace / drive.TotalSize * 100;
        // 磁碟空間低於 10% 就回報不健康
        return freeSpacePercent > 10
            ? HealthCheckResult.Healthy($"磁碟空間:{freeSpacePercent:F1}%")
            : HealthCheckResult.Unhealthy($"磁碟空間不足:{freeSpacePercent:F1}%");
    });

// 設定健康檢查路由
app.MapHealthChecks("/health", new HealthCheckOptions
{
    // 自訂回應格式(JSON)
    ResponseWriter = async (context, report) =>
    {
        context.Response.ContentType = "application/json";
        // 組合所有檢查結果
        var result = new
        {
            status = report.Status.ToString(),
            checks = report.Entries.Select(e => new
            {
                name = e.Key,
                status = e.Value.Status.ToString(),
                description = e.Value.Description
            })
        };
        // 序列化成 JSON 回傳
        await context.Response.WriteAsJsonAsync(result);
    }
});

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:把連線字串寫死在程式碼中

// ❌ 錯誤:連線字串寫死在程式碼中
var connectionString = "Server=myserver.database.windows.net;Database=mydb;User=admin;Password=P@ssw0rd123";
// 推上 Git 就洩漏了!

// ✅ 正確:從環境變數或設定檔讀取
// 開發環境:User Secrets
// dotnet user-secrets set "ConnectionStrings:Default" "Server=..."

// 正式環境:環境變數
// Railway/Azure 的 Dashboard 設定環境變數
var connectionString2 = builder.Configuration.GetConnectionString("Default");

❌ 錯誤 2:沒有健康檢查端點

❌ 問題:應用程式掛了但不知道

沒有健康檢查的後果:
├── 使用者反應「網站掛了」才知道有問題
├── 不知道是應用程式掛了還是資料庫掛了
├── Docker/K8s 無法自動重啟不健康的容器
└── 負載平衡器不知道要把流量導到哪裡

✅ 正確:設定 /health 端點
├── 檢查應用程式是否活著
├── 檢查資料庫連線是否正常
├── 檢查外部服務是否可用
├── Docker healthcheck 會定期呼叫
└── 監控系統可以監測並告警

❌ 錯誤 3:沒有設定 Logging

// ❌ 錯誤:用 Console.WriteLine 當日誌
Console.WriteLine("使用者登入了");  // 沒有時間戳、等級、結構化
Console.WriteLine($"錯誤:{ex.Message}");  // 沒有堆疊追蹤

// ✅ 正確:使用結構化日誌(Serilog)
// 有時間戳、日誌等級、結構化參數
logger.LogInformation("使用者 {UserId} 於 {LoginTime} 登入", userId, DateTime.UtcNow);
// 錯誤日誌包含完整的例外堆疊
logger.LogError(ex, "使用者 {UserId} 登入失敗", userId);

// Serilog 的好處:
// ├── 結構化日誌(可以搜尋、過濾)
// ├── 自動包含時間戳和日誌等級
// ├── 可以輸出到多個目標(Console、檔案、Seq、Elasticsearch)
// └── 效能優秀(非同步寫入)

💡 重點整理

概念 說明
Railway 簡單的雲端平台,適合學習和小型專案
Azure App Service 微軟的雲端應用程式代管服務
Reverse Proxy 反向代理(Nginx),轉發請求到後端
SSL/TLS Let's Encrypt 提供免費 SSL 憑證
Serilog .NET 的結構化日誌框架
Health Check 健康檢查端點,讓監控系統知道服務是否正常
環境變數 不同環境用不同的設定,不寫死在程式碼中

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊