visionA/docs/DEV-SETUP.md
jim800121chen eb66a7287a feat(deploy): visionA Cloud dev / stage docker compose + Caddy/nginx + 部署腳本
新增雲端版部署設定(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>
2026-05-01 11:22:44 +08:00

12 KiB
Raw Permalink Blame History

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 pnpmnpm i -g pnpm
jq 任意 brew install jqauto seed script 用)

確認:

docker --version          # >= 24.x
docker compose version    # >= v2.30
node --version            # >= 18 LTSpnpm 需要)

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 APIOIDC IdP
member-center-web visiona-dev-member-center-web 5060 MC 後台管理 UI
member-center-init visiona-dev-member-center-init 一次性 jobDB 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_idenv 是 CHANGE_ME),點登入會 fail。 需先在 MC 註冊一個 OAuth client。

5.1 走 MC Web UI推薦目前唯一可行路徑

為什麼不用 script 自動化MC password grant 在 Identity user 上有個 bugmandatory subject claim was missing),無法用 admin 帳密拿 token 呼叫 admin API。已於 MC 端開 issue。

步驟

  1. 開 MC 後台http://localhost:5060
  2. 登入
    • Email: admin@visiona.local
    • Password: Admin12345!
    • (這組密碼來自 .env.devMC_ADMIN_EMAIL / MC_ADMIN_PASSWORD
  3. 建 Tenant
    • 進「Tenants」頁
    • 點「Create」/「新增」
    • Name: visionA
    • Domains: visiona.cloud, localhost
    • Status: active
    • 送出
  4. 建 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
    • Redirect URIs: http://localhost:3721/api/auth/callback
    • 送出
  5. 取 client_id / client_secret
    • 建立成功後,畫面會顯示 client_idGUID 格式)
    • client_secretMC 應該會顯示一次(如果 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

  1. http://localhost:3000
  2. 點「登入」按鈕(會跳到 backend /api/auth/login
  3. backend 302 到 MC /oauth/authorize?...
  4. MC 顯示登入頁 → 用 admin@visiona.local / Admin12345! 登入 (或先在 MC Web UI 建 demo user 帳號)
  5. 同意授權後 redirect 回 http://localhost:3721/api/auth/callback?code=...
  6. backend 換 token、驗 id_token、建 session
  7. 302 回 frontend dashboardcookie 已設

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.devVISIONA_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-locallocal-tool 那一邊) 暫停 local-tool或在 .env.devVISIONA_API_PORT=3722 改一下、frontend 端 base URL 也改
5432 本機 PostgreSQL .env.devPOSTGRES_PORT=5433
5050 已被占用 .env.devMEMBER_CENTER_PORT=5051 + VISIONA_OIDC_ISSUER_URL=http://localhost:5051/
5060 已被占用 .env.devMEMBER_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 bugIdentity 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 執行階段問題

這節對應跑完 §5OAuth 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: headerfrontend fetch 必須 credentials: 'include'VISIONA_SESSION_SECRET 改過會讓舊 cookie 失效 → 清 cookie 重登
Pairing token 綁的 user_id 是 demo-user 而非 OIDC sub VISIONA_AUTH_TYPE 還是 static,或 OB5 沒拔乾淨 確認 .env.devVISIONA_AUTH_TYPE=oidc 且重啟過 visiona-apidocker 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. 已知 limitationPhase 0.6 雛形)

Limitation 影響 後續處理
MC admin API 無法注入 client_secret 必須走 Web UI 建 OAuth client MC 端開 issueadmin API 補 ClientSecret 欄位
MC password grant 缺 sub claim → 500 seed script 跑不通 MC 端開 issueTokenController 補 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.mdOIDC 接入 TDD§11 MC 端、§12 docker-compose、§13 env
  • .autoflow/04-architecture/adr/adr-010-oidc-bff.mdBFF 架構決策
  • visionA-backend/.env.examplevisionA-backend 完整 env 列表
  • ~/member_center/docs/INSTALL.mdMC installer 詳細用法