visionA/docker-compose.dev.yml
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

274 lines
12 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# visionA — 一鍵開發環境dev all-in-one
#
# 對應:.autoflow/04-architecture/oidc-tdd.md §12
#
# 服務拓撲:
#
# browser ──(3000)──▶ frontend (host: pnpm dev不在 compose 內)
# │
# ▼ /api/*fetch with cookie
# browser ──(3721)──▶ visiona-api ──(internal:3801)──▶ visiona-proxy
# │ ▲
# │ OIDC redirect / token │
# ▼ │
# member-center ──(5432)──▶ postgres │
# │ │
# (member-center-init) │
# local-agent (host: ./local-tool)
# │
# ▼
# WS 3800 → visiona-proxy
#
# 使用流程(詳見 docs/DEV-SETUP.md
#
# 1. 確認 ../member_center 與本 repo 同一層
# 2. 複製 .env.dev.example 成 .env.dev視需要調整
# 3. docker compose -f docker-compose.dev.yml up -d --build
# 4. 等所有 service healthydocker compose ps
# 5. 首次啟動完成後,依 docs/DEV-SETUP.md 「OAuth Client 註冊」一節,
# 手動建立 visionA OAuth clientMC admin API
# 6. 把產出的 client_id / client_secret 寫回 .env.dev → docker compose up -d 重啟 visiona-api
# 7. 另開 terminal: cd visionA-frontend && pnpm dev
# 8. 開瀏覽器 http://localhost:3000 → 點登入
#
# 一鍵清乾淨docker compose -f docker-compose.dev.yml down -v
#
# ⚠️ 此檔案僅供 dev 用,不要拿去 production。production 走 visionA-backend/docker/docker-compose.yml + IaC。
name: visiona-dev
services:
# ──────────────────────────────────────────────────────────
# PostgreSQL — 給 Member Center 用
# ──────────────────────────────────────────────────────────
postgres:
image: postgres:15-alpine
container_name: visiona-dev-postgres
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: membercenter
ports:
- "${POSTGRES_PORT:-5432}:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d membercenter"]
interval: 5s
timeout: 5s
retries: 20
start_period: 5s
networks:
- visiona-dev-net
# ──────────────────────────────────────────────────────────
# Member Center — Innovedus SSOOIDC provider
#
# ⚠️ build context 預設 ../member_center可用 MEMBER_CENTER_PATH 環境變數覆寫
# ──────────────────────────────────────────────────────────
member-center:
build:
context: ${MEMBER_CENTER_PATH:-../member_center}
dockerfile: src/MemberCenter.Api/Dockerfile
image: visiona/member-center-api:dev
container_name: visiona-dev-member-center
restart: unless-stopped
environment:
# Connection string雙底線 = ASP.NET Core 慣例的 nested key
ConnectionStrings__Default: "Host=postgres;Port=5432;Database=membercenter;Username=postgres;Password=postgres"
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: "http://+:5050"
# OIDC issuervisionA-backend 從 container 內以 http://member-center:5050 連,
# 但 browser 走 localhost。Issuer 必須跟 browser 看到的一致才能驗 id_token。
# 這裡用 http://localhost:5050 → discovery 與 id_token iss 都是這個值,
# 而 visionA-backend 改 hosts 指 /etc/hosts member-center → 127.0.0.1(見下方 extra_hosts
# 即可從 container 內也用 http://localhost:5050。
Auth__Issuer: "http://localhost:5050/"
Auth__AllowInsecureHttp: "true" # dev only — 允許 OIDC 跑 HTTP
Auth__MemberCenterAudience: "member_center_api"
Auth__SendEngineAudience: "send_engine_api"
ports:
- "${MEMBER_CENTER_PORT:-5050}:5050"
depends_on:
postgres:
condition: service_healthy
healthcheck:
# MC image (debian-slim) 沒裝 curl/wget改用 bash + /dev/tcp 測 TCP 連得上即可
# (能 connect 5050 即視為 aliveHTTP 200 驗證留給 caller 端做)
test:
- "CMD"
- "bash"
- "-c"
- "</dev/tcp/127.0.0.1/5050"
interval: 10s
timeout: 5s
retries: 30 # MC 冷啟動 + EF migrate 可能要 60s+
start_period: 30s
networks:
- visiona-dev-net
# ──────────────────────────────────────────────────────────
# Member Center Web — Admin UIRazor MVC
# 用來手動建 tenant + OAuth clientAPI 端目前無法注入 client_secret必須走 UI
# ──────────────────────────────────────────────────────────
member-center-web:
build:
context: ${MEMBER_CENTER_PATH:-../member_center}
dockerfile: src/MemberCenter.Web/Dockerfile
image: visiona/member-center-web:dev
container_name: visiona-dev-member-center-web
restart: unless-stopped
environment:
ConnectionStrings__Default: "Host=postgres;Port=5432;Database=membercenter;Username=postgres;Password=postgres"
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: "http://+:5060"
Auth__Issuer: "http://localhost:5050/"
Auth__AllowInsecureHttp: "true"
ports:
- "${MEMBER_CENTER_WEB_PORT:-5060}:5060"
depends_on:
postgres:
condition: service_healthy
member-center-init:
condition: service_completed_successfully
networks:
- visiona-dev-net
# ──────────────────────────────────────────────────────────
# Member Center init — 一次性 job
# 跑 installer initmigrate schema + 建 admin 帳號 + roles
# ──────────────────────────────────────────────────────────
member-center-init:
build:
context: ${MEMBER_CENTER_PATH:-../member_center}
dockerfile: src/MemberCenter.Installer/Dockerfile
image: visiona/member-center-installer:dev
container_name: visiona-dev-member-center-init
restart: "no" # 一次性
environment:
ConnectionStrings__Default: "Host=postgres;Port=5432;Database=membercenter;Username=postgres;Password=postgres"
ASPNETCORE_ENVIRONMENT: Development
command:
- init
- --no-prompt
- --verbose
- --admin-email
- ${MC_ADMIN_EMAIL:-admin@visiona.local}
- --admin-password
- ${MC_ADMIN_PASSWORD:-Admin12345!}
depends_on:
postgres:
condition: service_healthy
networks:
- visiona-dev-net
# ──────────────────────────────────────────────────────────
# visionA-backend / api-server
# ──────────────────────────────────────────────────────────
visiona-api:
build:
context: ./visionA-backend
dockerfile: docker/Dockerfile.api-server
image: visiona/api-server:dev
container_name: visiona-dev-api
restart: unless-stopped
environment:
VISIONA_HOST: "0.0.0.0"
VISIONA_API_PORT: "3721"
VISIONA_LOG_LEVEL: "${VISIONA_LOG_LEVEL:-info}"
# ──── Auth / OIDCPhase 0.6 起強制走 OIDC────
# OB52026-04-26已從 config 移除 VISIONA_AUTH_TYPE認證一律走 OIDC。
# backend 啟動時若以下 4 個 OIDC env 任何一個是 CHANGE_ME 會直接 fail。
# 第一次 `make dev-up` 後請依 DEV-SETUP.md §5 手動到 MC 註冊 OAuth client
# 把回傳的 client_id / client_secret 寫進 .env.dev 再 `make dev-rebuild visiona-api`。
# VISIONA_STATIC_USER_ID 仍保留作為 pairing 預設 userOIDC 未綁定時的 fallback
VISIONA_STATIC_USER_ID: "${VISIONA_STATIC_USER_ID:-demo-user}"
# 從 backend container 內看 MC 的 issuer / endpoints。
# 注意issuer 必須和 browser 看到的一致http://localhost:5050
# 因此 container 內 /etc/hosts 把 localhost → host gateway
# 這樣 backend container 內 fetch http://localhost:5050 → 走 host port → MC。
# ⚠️ 必須包含 trailing slash — MC discovery 回的 issuer 帶 slash不一致會導致 client 拒絕 init
VISIONA_OIDC_ISSUER_URL: "${VISIONA_OIDC_ISSUER_URL:-http://localhost:5050/}"
VISIONA_OIDC_CLIENT_ID: "${VISIONA_OIDC_CLIENT_ID:-CHANGE_ME}"
VISIONA_OIDC_CLIENT_SECRET: "${VISIONA_OIDC_CLIENT_SECRET:-CHANGE_ME}"
VISIONA_OIDC_REDIRECT_URL: "${VISIONA_OIDC_REDIRECT_URL:-http://localhost:3721/api/auth/callback}"
VISIONA_OIDC_SCOPES: "openid email profile"
VISIONA_FRONTEND_URL: "${VISIONA_FRONTEND_URL:-http://localhost:3000}"
VISIONA_SESSION_SECRET: "${VISIONA_SESSION_SECRET:-please-change-me-32-bytes-random-dev-secret}"
# ──── 既有設定 ────
VISIONA_PROXY_INTERNAL_URL: "http://visiona-proxy:3801"
VISIONA_CORS_ALLOWED_ORIGINS: "${VISIONA_CORS_ALLOWED_ORIGINS:-http://localhost:3000}"
VISIONA_SEED_DEMO_DATA: "${VISIONA_SEED_DEMO_DATA:-true}"
VISIONA_STORAGE_BACKEND: "localfs"
VISIONA_STORAGE_LOCALFS_ROOT: "/app/data/storage"
VISIONA_STORAGE_LOCALFS_BASE_URL: "${VISIONA_STORAGE_BASE_URL:-http://localhost:3721/storage}"
VISIONA_STORAGE_BASE_URL: "${VISIONA_STORAGE_BASE_URL:-http://localhost:3721/storage}"
VISIONA_STORAGE_SIGNING_SECRET: "${VISIONA_STORAGE_SIGNING_SECRET:-dev-signing-secret-change-me}"
VISIONA_MODEL_MAX_SIZE_MB: "100"
ports:
- "${VISIONA_API_PORT:-3721}:3721"
extra_hosts:
# 讓 container 內可以用 http://localhost:5050 連到 host 的 MC port
# 確保 OIDC issuer 在 backend ↔ browser 兩邊看起來一致。
- "localhost:host-gateway"
volumes:
- api-storage:/app/data/storage
depends_on:
visiona-proxy:
condition: service_healthy
member-center:
condition: service_healthy
member-center-init:
condition: service_completed_successfully
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:3721/healthz"]
interval: 30s
timeout: 3s
start_period: 10s
retries: 3
networks:
- visiona-dev-net
# ──────────────────────────────────────────────────────────
# visionA-backend / remote-proxy
# ──────────────────────────────────────────────────────────
visiona-proxy:
build:
context: ./visionA-backend
dockerfile: docker/Dockerfile.remote-proxy
image: visiona/remote-proxy:dev
container_name: visiona-dev-proxy
restart: unless-stopped
environment:
VISIONA_HOST: "0.0.0.0"
VISIONA_TUNNEL_PORT: "3800"
VISIONA_PROXY_INTERNAL_PORT: "3801"
VISIONA_LOG_LEVEL: "${VISIONA_LOG_LEVEL:-info}"
VISIONA_PAIRING_TOKEN: "${VISIONA_PAIRING_TOKEN:-}"
ports:
- "${VISIONA_TUNNEL_PORT:-3800}:3800"
# internal 3801 預設不對外
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:3800/healthz"]
interval: 30s
timeout: 3s
start_period: 10s
retries: 3
networks:
- visiona-dev-net
volumes:
pgdata:
name: visiona-dev-pgdata
api-storage:
name: visiona-dev-api-storage
networks:
visiona-dev-net:
name: visiona-dev-net
driver: bridge