目錄
- Docker 基礎概念
- 環境準備
- SQL Server 容器設定
- .NET Core API 容器化
- Vue3 前端容器化
- 使用 Docker Compose 整合服務
- 資源管理與監控
- 開發環境設定
- 網路配置
- 部署與維護
- 常見問題與解決方案
Docker 基礎概念
Docker 是一個開放原始碼的容器化平台,把應用程式和它的依賴項打包成獨立容器,讓同一份設定在本機開發、測試機、正式環境都能跑出相同結果。
這套技術組合讓你少踩的坑:「我本機跑得起來但 CI 就壞了」基本上絕跡、新人 onboarding 不再花一天裝環境、版本升級可以先切新容器測試再切流量。代價是要花時間學 Dockerfile 和 Compose 的寫法——這篇就是從這裡開始。
環境準備
安裝 Docker Desktop
1
2
3
4
5
6
7
8# Windows/Mac 下載並安裝 Docker Desktop
https://www.docker.com/products/docker-desktop/
# Linux (Ubuntu) 安裝 Docker Engine
sudo apt update
sudo apt install docker.io
sudo systemctl start docker
sudo systemctl enable docker確認安裝
1
2
3
4
5
6
7docker --version
# Docker Desktop 現在預設附的是 Compose v2 plugin,用這個
docker compose version
# 若你環境裡還裝著舊的 v1 獨立執行檔,以下指令才有效
# docker-compose --version
SQL Server 容器設定
- 建立 SQL Server 容器
1
2
3
4
5
6docker pull mcr.microsoft.com/mssql/server:2022-latest
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=YourStrong@Passw0rd" \
-p 1433:1433 --name sqlserver \
-v sqlvolume:/var/opt/mssql \
-d mcr.microsoft.com/mssql/server:2022-latest
兩個地方值得留意。一是密碼環境變數:舊文常見的 SA_PASSWORD 已被標記為 deprecated,新版映像建議改用 MSSQL_SA_PASSWORD,舊名目前仍可動但會跳警告。二是映像版本:這裡用 2022-latest。SQL Server 2019 主流支援已於 2025 年 2 月 28 日結束,截至本文更新時 SQL Server 2022 是當前正式版(2025 仍在 preview),新專案直接從 2022 起步。
- 確認容器運行狀態
1
docker ps
.NET Core API 容器化
版本提醒:本文示範用 .NET 6。.NET 6 已於 2024 年 11 月終止支援,新專案請改用仍在支援週期內的 LTS(例如 .NET 8 或更新版),把下面 sdk:6.0 / aspnet:6.0 的 tag 換掉即可,其餘結構不變。
- 在專案根目錄建立 Dockerfile
1
2
3
4
5
6
7
8
9
10
11FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["YourProject.csproj", "./"]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "YourProject.dll"]
這裡用 dotnet publish 而不是 dotnet build。publish 會輸出真正要部署的執行期資產(已做發佈最佳化、不含開發期多餘檔案),是容器化的標準做法;build 的輸出帶著開發期檔案、也缺少 publish 的處理,丟進 runtime 映像並不適合。
設定 .dockerignore
1
2
3bin/
obj/
node_modules/建立映像檔和容器
1
2docker build -t myapi .
docker run -d -p 5000:80 --name api myapi
Vue3 前端容器化
- 建立 Dockerfile(
node:16早已 EOL,這裡用仍在支援的 LTS)1
2
3
4
5
6
7
8
9
10
11FROM node:20 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
若 Vue Router 使用 history mode(createWebHistory),直接訪問非根路徑或在瀏覽器重新整理會得到 nginx 404,因為伺服器找不到對應靜態檔案。解法是在容器裡放自訂 nginx 設定,讓所有路徑都回到 index.html:
1 | # nginx.conf(放在專案根,COPY 進映像) |
Dockerfile 對應改成:
1 | FROM nginx:stable-alpine as production-stage |
若用的是 createWebHashHistory(URL 帶 #),nginx 不需要額外設定,但一般正式環境會用 history mode。
- 建立映像檔和容器
1
2docker build -t myvue .
docker run -d -p 8080:80 --name frontend myvue
使用 Docker Compose 整合服務
建立 docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42version: '3.8'
services:
db:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
- ACCEPT_EULA=Y
- MSSQL_SA_PASSWORD=YourStrong@Passw0rd
ports:
- "1433:1433"
volumes:
- sqlvolume:/var/opt/mssql
healthcheck:
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -C -Q \"SELECT 1\" || exit 1"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
api:
build:
context: ./api
dockerfile: Dockerfile
ports:
- "5000:80"
depends_on:
db:
condition: service_healthy
environment:
- ConnectionStrings__DefaultConnection=Server=db;Database=YourDB;User=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "8080:80"
depends_on:
- api
volumes:
sqlvolume:啟動所有服務
1
docker compose up -d
檢查服務狀態
1
docker compose ps
這裡有個首次啟動最常卡住的地方。depends_on 預設只保證容器的「啟動順序」,不會等到 SQL Server「真的可以連線」才放行——而 SQL Server 容器初始化要數十秒。如果 API 一啟動就連 DB,這段空窗期會連不上而崩掉。所以上面 db 服務加了 healthcheck,API 的 depends_on 也改成 condition: service_healthy,讓 API 等到資料庫健康檢查通過才啟動。healthcheck 用的是 SQL Server 2022 映像內建的 /opt/mssql-tools18/bin/sqlcmd(2022 CU14 起改放在 mssql-tools18),-C 旗標用來信任容器的自簽憑證。test 指令裡密碼寫成 $$MSSQL_SA_PASSWORD 而非 $MSSQL_SA_PASSWORD:Compose 在讀取 YAML 時會對 $ 開頭的字串做變數替換,雙 $$ 是跳脫寫法,讓 Compose 把它當作字面的 $MSSQL_SA_PASSWORD 傳給 shell,由 shell 在容器內展開;若只寫單 $,Compose 會在啟動前嘗試從主機環境讀取這個變數,讀不到就傳入空字串,導致 sqlcmd 認證失敗。光靠 healthcheck 仍不能完全取代 API 端的連線重試,正式環境兩者都要做。
資源管理與監控
容器資源限制
下面的片段展示如何替主 docker-compose.yml 裡的 api 和 db 服務加上資源限制。注意:db 區塊僅列出 deploy 與 volumes 這兩個新增欄位,要疊加到主檔的 db 服務上,而非整段替換——原本的 image、environment、ports、healthcheck 一行都不能刪,否則資料庫起不來。
1 | # 以下為疊加片段,請合併至主 docker-compose.yml 對應服務,勿直接整段貼上取代 |
關於 deploy.resources 的支援範圍:Docker Compose v2 CLI(docker compose,現在 Docker Desktop 預設附帶的外掛版)在非 Swarm 模式下直接支援這些資源限制,設完就有效。但若你的環境還是用舊的 docker-compose v1 獨立執行檔(Python 版),deploy 區塊的 resources 在非 Swarm 模式預設被忽略,需要加 --compatibility flag 才能讓限制生效:docker-compose --compatibility up -d。可以用 docker compose version 和 docker-compose --version 分別確認你裝的是哪個版本。
要限制資料庫佔用的磁碟空間,實務上做法是控制掛載 volume 所在的底層磁碟或分割區配額,而非依賴 Compose 的 storage_opt——storage_opt 只在特定 storage driver(如 devicemapper)下有效,overlay2 等常見驅動並不支援。
監控工具設定
1. Portainer 安裝
Portainer 提供了直覺的 Web 介面來管理 Docker 容器:
1 | docker volume create portainer_data |
安裝完成後可以訪問 http://localhost:9000 進行設定。
2. 日誌管理
在 docker-compose.yml 中加入日誌設定:
1 | services: |
開發環境設定
Hot Reload 配置
Hot Reload 讓你改完程式碼不用重建映像就能看到效果,但這裡有個容易踩到的坑:dotnet watch 是 SDK 才有的工具,前面 Dockerfile 最終階段用的 mcr.microsoft.com/dotnet/aspnet:6.0 是純 runtime 映像,裡面沒有 dotnet watch,直接套這段 override 會噴 command not found。
所以開發環境要讓 API 跑在 SDK 映像上。做法是在 Dockerfile 多留一個 build 階段當作開發目標,override 改成 build 這個階段:
1 | # Dockerfile 在原本的 build 階段後,可直接把 build 階段當開發用映像 |
1 | # docker-compose.override.yml |
dotnet watch run 在較新版本可簡寫成 dotnet watch(會預設執行 run),兩種寫法都行。掛載路徑要對齊 build 階段的 WORKDIR /src,不要沿用 runtime 階段的 /app,否則 watch 監看的目錄會對不上原始碼。
Debug 設定
在 VS Code 中設定容器除錯,前提是容器內 /vsdbg/vsdbg 必須存在——aspnet runtime 映像本身不含 vsdbg,需要額外準備。有兩種方式:
方式一:在 Dockerfile 安裝 vsdbg
在 SDK/build 階段(也就是 FROM mcr.microsoft.com/dotnet/sdk:... AS build 那個階段)加入這行,不要加在 runtime 階段——runtime 映像沒有 apt-get 也不需要偵錯器:
1 | RUN apt-get update && apt-get install -y --no-install-recommends curl unzip \ |
方式二:以 volume 掛入本機下載好的 vsdbg
映像本身不需要包含偵錯器。先在本機下載 vsdbg:
1 | # Linux/macOS |
1 | # Windows:直接在 VS Code 對容器做一次 C# Dev Kit attach, |
然後在 docker-compose.override.yml 把本機 vsdbg 路徑掛進容器:
1 | services: |
Windows 路徑寫法:
1 | volumes: |
確認 vsdbg 已就位後,在 .vscode/launch.json 加入以下設定即可 attach:
1 | // .vscode/launch.json |
debuggerPath 填 /vsdbg/vsdbg(Linux 容器內路徑,不受本機 OS 影響)。不論哪種方式,都要確認此路徑確實存在於容器內,否則 attach 會報路徑找不到而失敗。
網路配置
網路隔離
讓前端和資料庫永遠不在同一個網路裡,是隔離洩漏風險最直接的做法。把服務分進不同 network:
1 | # docker-compose.yml |
部署與維護
CI/CD 整合
以下是一個使用 GitHub Actions 的範例:
1 | # .github/workflows/docker-publish.yml |
環境變數管理
建立不同環境的設定檔:
1 | # .env.development |
自動化部署腳本
1 |
|
常見問題與解決方案
1. SQL Server 連線問題
- 確認連線字串中的主機名稱使用服務名稱 (db)
- 確認 SA 密碼符合複雜度要求
- 檢查防火牆設定
- 連線字串記得帶
TrustServerCertificate=True。從 Microsoft.Data.SqlClient 4.0(.NET 6 時期)起,連 SQL Server 預設Encrypt=true,會去驗證伺服器憑證。容器裡的 SQL Server 用的是自簽憑證,用戶端不信任就會丟「The certificate chain was issued by an authority that is not trusted」而連不上。這也是這個技術棧最常見的「照抄就壞」狀況。正式環境若要真正加密,應該換成受信任的憑證,而不是長期靠TrustServerCertificate=True跳過驗證。
2. .NET Core API 容器化問題
- 專案名稱不符:Dockerfile 裡
COPY ["YourProject.csproj", "./"]和ENTRYPOINT ["dotnet", "YourProject.dll"]的名稱必須和實際.csproj檔名一致,否則dotnet restore或dotnet publish會靜默失敗或 entrypoint 找不到 DLL。先用docker build看 build log,或進容器ls /app/publish確認輸出檔名。 - ASPNETCORE_URLS 問題:容器內 API 預設監聽
http://+:80(aspnet 映像設定),若你本機跑慣了https://localhost:5001直接複製 URL 訪問會連不上。確認docker-compose.yml裡 ports 的容器端(右邊)和ASPNETCORE_URLS或ASPNETCORE_HTTP_PORTS對齊,例如 ports5000:80對應ASPNETCORE_URLS=http://+:80。 - 埠號衝突:本機已有其他服務佔用 5000 port 時,
docker run -p 5000:80會直接報address already in use。改換主機端口(左邊數字)即可,容器內部不受影響。
3. Vue3 建置問題
- node_modules 沒被忽略:若
.dockerignore漏了node_modules/,COPY . .會把本機的 node_modules 全複製進去,覆蓋npm install的結果,且映像會暴增幾百 MB。確認.dockerignore有這一行,並用docker build --no-cache重新建置。 - 環境變數
.env問題:Vite 建置時只把VITE_前綴的變數打包進靜態檔,普通的環境變數不會帶入。若前端無法讀到 API URL,確認.env裡是VITE_API_URL=...而非API_URL=...,並確認.env在COPY . .之前已存在(或改在npm run build時以--mode指定)。
4. 效能優化相關
映像檔優化
- 多階段建置:.NET Core 多階段建置後,映像從約 800 MB(SDK)壓到 200 MB 以內(aspnet runtime)。確認 Dockerfile 最終
FROM用的是aspnet而非sdk。 - alpine 基底:nginx 用
nginx:stable-alpine(約 23 MB)而非nginx:stable(約 142 MB)。.NET Core 的aspnet:8.0-alpine比標準版再小 50-70 MB,但要注意 alpine 用的是 musl libc,少數 NuGet 套件有相容問題,遇到再換回標準版。 - .dockerignore:至少要排
bin/、obj/、.git/、node_modules/。可以用docker history <image>觀察每個 layer 大小,找出意外肥大的 COPY 步驟。
容器效能優化
- 資源限制設定後確認有效:
docker stats會顯示每個容器的 CPU/記憶體即時使用量與上限,設完就用這個確認限制是否生效。 - Volume vs bind mount:Volume 走 Docker 管理路徑,在 macOS/Windows Docker Desktop 上比 bind mount 快 2-5 倍(bind mount 需要跨 VM 同步)。資料庫資料一定用 Volume,原始碼開發時才用 bind mount。
- 清理指令:
docker system prune -f一次清掉停止的容器、無用 network 和 dangling image;加-a會連沒在用的 image 一起刪,空間不夠時再用。
網路效能
- 同一個 Compose 專案的服務自動在同一個 bridge network 裡,用服務名稱(如
db、api)互連,不需要走 host port,延遲遠低於繞出去再繞回來。 - 需要前後端隔離時,按本文「網路配置」一節建立
backend internal: true,只讓 API 跨兩個 network,不要讓 frontend 直接碰到 db。
安全性建議
除了之前提到的最佳實踐外,還要注意:
容器安全
1 | services: |
read_only: true 把容器的根檔案系統設成唯讀,能防止惡意程序寫入。但 ASP.NET 的 runtime 需要在 /tmp 寫入暫存檔,不另外掛 tmpfs 直接加 read_only: true 會讓 API 起不來。上面這段已同時掛了 tmpfs: - /tmp,兩者要配合使用。
非 root 使用者
「使用非 root 使用者運行容器」是常見建議,但要在 Dockerfile 裡真的寫出來。以 .NET Core 映像為例:
1 | FROM mcr.microsoft.com/dotnet/aspnet:8.0 |
SQL Server 官方映像(mssql/server)已內建用 mssql 使用者跑,不需要額外設定。
機密管理
使用 Docker Secrets:
1 | services: |
最佳實踐建議
安全性考量
- 不要在映像檔中存儲敏感資訊
- 定期更新基礎映像檔
- 使用非 root 使用者運行容器
開發流程
- 使用 docker-compose.override.yml 管理開發環境設定
- 實作健康檢查機制
- 建立完整的 CI/CD 流程
結論
本文涵蓋了以下幾個核心部分:可實際運行的 docker-compose.yml(含 healthcheck 確保 API 在資料庫就緒後才啟動)、Hot Reload 開發環境設定、nginx history mode 路由修正,以及常見問題的排查方向。
後續建議優先處理 .env 分層管理(開發/staging/正式環境各自獨立)與 CI/CD 自動建置推送。這兩項到位後,本機與正式環境的差異將收斂至環境變數層面,可有效避免因環境不一致引發的部署問題。




