新增雲端版部署設定(Phase 0.6 dev + Phase 0.7 stage 分兩套): dev 環境(docker-compose.dev.yml): - 5 service all-in-one(postgres + member-center + visionA-backend + frontend + Caddy) - Caddy 自動 HTTPS for localhost - .env.dev.example 範本(使用者拷出 .env.dev 後 docker compose up -d) - Makefile dev-with-mc 9 個 target stage 環境(docker-compose.stage.yml + docker/Dockerfile.stage): - multi-stage build(node22 frontend + go1.26 backend × 2 + nginx-alpine runtime) 最終 image 319 MB,含 nginx + nodejs + tini + bash - entrypoint.stage.sh 4 process 共命運(nginx + api-server + remote-proxy + next.js standalone)用 wait -n + SIGTERM trap - nginx.stage.conf:白名單 server_name stage-9527.innovedus.com + 444 default_server + /healthz 例外(127.0.0.0/8 only)+ /api/ 與 /storage/ 強制 no-store + /tunnel/connect WS upgrade + 100M body / 3600s timeout - 對外 mapping 0.0.0.0:9527:80(公司 host nginx 在外層處理 HTTPS termination — Let's Encrypt stage-9527.innovedus.com 自動續簽) - named volume visiona-data(不用 bind mount,因 stage docker daemon 在 host root 無 mkdir 權限) 部署腳本(scripts/deploy-stage.sh): - 仿 edge-ai-platform/scripts/deploy-docker.sh 早期 save/load 模式 - 為什麼不用 internal registry:公司 192.168.0.130:5000 開了 auth、無帳密 - 流程:buildx --load → docker save | gzip → DOCKER_HOST docker load → compose up - 含 --rollback <tag> / --skip-build / --no-push / --skip-deploy 選項 - timestamp + git SHA tag 留 rollback 餘地 文件(docs/): - DEV-SETUP.md:dev 環境一鍵起步驟 - SMOKE-TEST.md:手動煙測 checklist(OIDC flow / pairing / tunnel) - STAGE-DEPLOY.md:stage 完整手冊(架構圖 / 環境前置 / 部署 step / rollback / 7 種故障排除 / 緊急救回 POC) .env.stage.example 對齊 backend A1 改造: - VISIONA_OIDC_CLIENT_SECRET 留空(PKCE-only public client) - VISIONA_OIDC_SERVICE_CLIENT_ID/_SECRET 留空(Phase 1 預留鉤子) - 所有 secret 用 placeholder(CHANGE_ME_OPENSSL_RAND_HEX_32) .dockerignore:避免 node_modules / .next / .git 等進 build context Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
visionA Dev 環境設置(一鍵 docker compose)
對應
.autoflow/04-architecture/oidc-tdd.md§12 + Phase 0.6 OD1 任務適用對象:剛 clone 下來、第一次想跑起 visionA + Member Center OIDC flow 的開發者
預期時間:第一次 build ~10 分鐘;之後重啟 < 30 秒
TL;DR
# 1. 確認 ../member_center 與本 repo 平行擺放
# visionA/ 與 member_center/ 在同一個 parent dir
ls -d ../member_center # 應該存在
# 2. 複製 env 範本
cp .env.dev.example .env.dev
# 視情況編輯(多半預設值即可)
# 3. 起所有 service
docker compose -f docker-compose.dev.yml --env-file .env.dev up -d --build
# 第一次:~10 分鐘(dotnet 8 build + go build)
# 之後 :< 30 秒
# 4. 等所有 service healthy(< 1 分鐘)
docker compose -f docker-compose.dev.yml ps
# 全部要看到 (healthy)
# 5. 註冊 visionA OAuth client(手動,~2 分鐘,見下方第 5 節)
# 6. 把 client_id 寫進 .env.dev → 重啟 api
docker compose -f docker-compose.dev.yml --env-file .env.dev up -d visiona-api
# 7. 另開 terminal 跑 frontend
cd visionA-frontend && pnpm install && pnpm dev
# 8. 開 http://localhost:3000 → 點登入 → 完成 OIDC flow
1. 前置需求
| 工具 | 版本 | 安裝 |
|---|---|---|
| Docker Desktop | 最新 | https://docker.com/products/docker-desktop |
| Docker Compose | v2.30+ | 通常隨 Docker Desktop |
| pnpm | 9+ | brew install pnpm 或 npm i -g pnpm |
| jq | 任意 | brew install jq(auto seed script 用) |
確認:
docker --version # >= 24.x
docker compose version # >= v2.30
node --version # >= 18 LTS(pnpm 需要)
2. 目錄結構假設
parent/
├── visionA/ ← 你在這(this repo)
│ ├── docker-compose.dev.yml
│ ├── .env.dev.example
│ ├── visionA-backend/
│ └── visionA-frontend/
└── member_center/ ← 平行擺放(用 git clone)
└── src/MemberCenter.Api/Dockerfile
如果你的 member_center 在別處:
# 編輯 .env.dev
MEMBER_CENTER_PATH=/Users/me/code/member_center
3. 服務拓撲
| Service | Container | Host port | 用途 |
|---|---|---|---|
| postgres | visiona-dev-postgres | 5432 | MC 的資料庫 |
| member-center | visiona-dev-member-center | 5050 | MC API(OIDC IdP) |
| member-center-web | visiona-dev-member-center-web | 5060 | MC 後台管理 UI |
| member-center-init | visiona-dev-member-center-init | – | 一次性 job:DB migrate + 建 admin |
| visiona-proxy | visiona-dev-proxy | 3800 (tunnel WS) | local agent 連入 |
| visiona-api | visiona-dev-api | 3721 | visionA 後端 API |
| frontend | (host pnpm dev) | 3000 | Next.js dev server |
4. 起環境步驟
4.1 第一次(含 build)
cd /path/to/visionA
cp .env.dev.example .env.dev
# Build 所有 image(耐心等 ~10 分鐘)
docker compose -f docker-compose.dev.yml --env-file .env.dev build
# 起來
docker compose -f docker-compose.dev.yml --env-file .env.dev up -d
4.2 確認都健康
docker compose -f docker-compose.dev.yml ps
預期看到:
NAME STATUS
visiona-dev-postgres Up (healthy)
visiona-dev-member-center Up (healthy)
visiona-dev-member-center-web Up
visiona-dev-member-center-init Exited (0) ← 正常,一次性 job
visiona-dev-proxy Up (healthy)
visiona-dev-api Up (healthy)
如果 visiona-api 卡在 unhealthy / restarting → 看 §7 故障排除。
4.3 確認 OIDC discovery 通
curl -s http://localhost:5050/.well-known/openid-configuration | head -c 200
應該看到 JSON,含 "issuer":"http://localhost:5050/"。
5. 註冊 visionA OAuth Client(必做)
第一次 build 起來時 visionA-api 還沒有合法的 OIDC client_id(env 是 CHANGE_ME),點登入會 fail。
需先在 MC 註冊一個 OAuth client。
5.1 走 MC Web UI(推薦,目前唯一可行路徑)
為什麼不用 script 自動化:MC password grant 在 Identity user 上有個 bug(
mandatory subject claim was missing),無法用 admin 帳密拿 token 呼叫 admin API。已於 MC 端開 issue。
步驟:
- 開 MC 後台:http://localhost:5060
- 登入:
- Email:
admin@visiona.local - Password:
Admin12345! - (這組密碼來自
.env.dev的MC_ADMIN_EMAIL/MC_ADMIN_PASSWORD)
- Email:
- 建 Tenant:
- 進「Tenants」頁
- 點「Create」/「新增」
- Name:
visionA - Domains:
visiona.cloud, localhost - Status:
active - 送出
- 建 OAuth Client:
- 進「OAuth Clients」頁
- 點「Create」
- Tenant: 選剛剛建的
visionA - Name:
visionA Cloud - Client Type:
confidential - Usage:
webhook_outbound- (MC 目前沒有
web_app類型,雛形借用webhook_outbound,是唯一允許 redirect_uris 的非 client-credentials usage;對應 oidc-tdd §11.2)
- (MC 目前沒有
- Redirect URIs:
http://localhost:3721/api/auth/callback - 送出
- 取 client_id / client_secret:
- 建立成功後,畫面會顯示 client_id(GUID 格式)
- client_secret:MC 應該會顯示一次(如果 UI 沒給 secret,是 MC 的另一個 limitation;雛形階段可改成
client_type=public不帶 secret)
5.2 把 client_id / secret 寫進 .env.dev
# 編輯 .env.dev
VISIONA_OIDC_CLIENT_ID=<剛剛拿到的 client_id GUID>
VISIONA_OIDC_CLIENT_SECRET=<剛剛拿到的 secret,沒拿到就留空>
5.3 重啟 visiona-api
docker compose -f docker-compose.dev.yml --env-file .env.dev up -d visiona-api
docker compose -f docker-compose.dev.yml logs visiona-api --tail 5
預期看到:
{"msg":"OIDC initialized","issuer":"http://localhost:5050/","client_id":"<你的 guid>","redirect_url":"http://localhost:3721/api/auth/callback"}
{"msg":"api-server listening","addr":"0.0.0.0:3721"}
6. 跑 Frontend + 完成 demo flow
# 另開 terminal
cd visionA-frontend
pnpm install
pnpm dev
# → http://localhost:3000
Demo flow:
- 開 http://localhost:3000
- 點「登入」按鈕(會跳到 backend /api/auth/login)
- backend 302 到 MC
/oauth/authorize?... - MC 顯示登入頁 → 用
admin@visiona.local / Admin12345!登入 (或先在 MC Web UI 建 demo user 帳號) - 同意授權後 redirect 回
http://localhost:3721/api/auth/callback?code=... - backend 換 token、驗 id_token、建 session
- 302 回 frontend dashboard,cookie 已設
7. 故障排除
7.1 visiona-api 卡 unhealthy / restarting
最常見:OIDC 沒設好 → backend crash loop。
docker compose -f docker-compose.dev.yml logs visiona-api --tail 30
| 錯誤關鍵字 | 原因 | 解法 |
|---|---|---|
discovery fetch failed |
MC 還沒起來 | 等 30 秒再看;或 docker compose ps member-center 確認 healthy |
issuer URL ... did not match |
issuer trailing slash 不一致 | 確認 .env.dev 的 VISIONA_OIDC_ISSUER_URL=http://localhost:5050/(斜線結尾) |
IssuerURL invalid / ClientID required |
env 沒帶到 | 確認啟動時用 --env-file .env.dev |
connection refused |
container 內找不到 host | 不應該發生(compose 已加 extra_hosts: localhost:host-gateway) |
7.2 port 衝突
| port | 衝突來源 | 解法 |
|---|---|---|
| 3721 | 本機跑著 visionA-local(local-tool 那一邊) | 暫停 local-tool,或在 .env.dev 把 VISIONA_API_PORT=3722 改一下、frontend 端 base URL 也改 |
| 5432 | 本機 PostgreSQL | .env.dev 改 POSTGRES_PORT=5433 |
| 5050 | 已被占用 | .env.dev 改 MEMBER_CENTER_PORT=5051 + VISIONA_OIDC_ISSUER_URL=http://localhost:5051/ |
| 5060 | 已被占用 | .env.dev 改 MEMBER_CENTER_WEB_PORT=5061 |
7.3 MC build 失敗 / Dockerfile 錯誤
如果 MEMBER_CENTER_PATH 路徑不對:
# 確認 path
ls -d ${MEMBER_CENTER_PATH:-../member_center}
# 用絕對路徑
MEMBER_CENTER_PATH=/Users/you/code/member_center docker compose -f docker-compose.dev.yml build member-center
7.4 MC password grant 500 error
System.InvalidOperationException: The specified principal was rejected
because the mandatory subject claim was missing.
已知 MC bug(Identity user 缺 sub claim)。Phase 0.6 雛形請走 admin Web UI 註冊 OAuth client,不用 password flow。
7.5 一鍵完全重置
docker compose -f docker-compose.dev.yml down -v
# -v 會刪除 volumes(包括 postgres data + visiona storage)
# 下次 up -d 時會重新跑 member-center-init 建 admin 帳號
7.6 OIDC flow 執行階段問題
這節對應跑完 §5(OAuth client 註冊)後,實際走 login flow 卻卡住的情境。 詳細逐步驗證見
docs/SMOKE-TEST.md,這裡只列高頻錯誤對照表。
| 症狀 | 通常原因 | 解法 |
|---|---|---|
點登入後 callback 噴 state_mismatch |
visiona_pending_sid cookie 沒帶過去 |
確認 VISIONA_FRONTEND_URL 與 frontend 真實 URL 一致;不要設 cookie Domain;瀏覽器 DevTools → Application → Cookies 看 cookie 是否存進去 |
callback 噴 pending_session_not_found |
pending 過 10 分鐘 TTL,或 backend 重啟清掉 in-memory store | 重新點登入;雛形 in-memory 重啟即清空 |
callback 噴 id_token_invalid |
iss / aud / nonce 對不上,或 JWKS cache 過時 | 看 backend log detail 欄;嘗試 docker compose restart visiona-api 清 JWKS cache |
callback 噴 token_exchange_failed |
client_secret 不對 / redirect_uri 與 MC 設定不一致 | 比對 .env.dev 與 MC admin UI 中 client 設定 |
GET /api/auth/me 永遠 401 |
cookie 沒帶 / cookie 簽章對不上 / session 過期 | DevTools 看 request 是否帶 Cookie: header;frontend fetch 必須 credentials: 'include';VISIONA_SESSION_SECRET 改過會讓舊 cookie 失效 → 清 cookie 重登 |
Pairing token 綁的 user_id 是 demo-user 而非 OIDC sub |
VISIONA_AUTH_TYPE 還是 static,或 OB5 沒拔乾淨 |
確認 .env.dev 是 VISIONA_AUTH_TYPE=oidc 且重啟過 visiona-api;docker compose logs visiona-api | grep -i pairing 看實際 binding |
| 登出後仍能訪問受保護頁面 | Set-Cookie: visiona_session=; Max-Age=0 沒設對,或 frontend route guard 沒擋 |
DevTools 確認 cookie 真的被清;訪 /api/auth/me 不帶 cookie 應 401 |
8. 常用命令
# 看所有 service log
docker compose -f docker-compose.dev.yml logs -f
# 看單一 service
docker compose -f docker-compose.dev.yml logs -f visiona-api
# 重啟單一 service
docker compose -f docker-compose.dev.yml restart visiona-api
# 進 postgres
docker exec -it visiona-dev-postgres psql -U postgres -d membercenter
# 看 OpenIddict apps
docker exec -it visiona-dev-postgres psql -U postgres -d membercenter \
-c 'SELECT "ClientId", "DisplayName", "ClientType", "RedirectUris" FROM "OpenIddictApplications";'
# 完全停止(保留 data)
docker compose -f docker-compose.dev.yml down
# 完全停止 + 清乾淨
docker compose -f docker-compose.dev.yml down -v
# 重 build(程式碼變了)
docker compose -f docker-compose.dev.yml up -d --build visiona-api
9. 已知 limitation(Phase 0.6 雛形)
| Limitation | 影響 | 後續處理 |
|---|---|---|
| MC admin API 無法注入 client_secret | 必須走 Web UI 建 OAuth client | MC 端開 issue:admin API 補 ClientSecret 欄位 |
| MC password grant 缺 sub claim → 500 | seed script 跑不通 | MC 端開 issue:TokenController 補 Subject claim mapping |
MC 只有 webhook_outbound 支援 redirect_uris |
usage 命名語意不對(雛形借用) | MC 端開 issue:新增 web_app usage(對應 oidc-tdd §11.2) |
| host visiona-local 占 3721 → 跟 docker compose 衝突 | 兩個只能擇一跑 | 預期;docs 已說明 |
10. 相關文件
.autoflow/04-architecture/oidc-tdd.md:OIDC 接入 TDD(§11 MC 端、§12 docker-compose、§13 env).autoflow/04-architecture/adr/adr-010-oidc-bff.md:BFF 架構決策visionA-backend/.env.example:visionA-backend 完整 env 列表~/member_center/docs/INSTALL.md:MC installer 詳細用法