新增雲端版部署設定(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>
274 lines
12 KiB
YAML
274 lines
12 KiB
YAML
# 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 healthy(docker compose ps)
|
||
# 5. 首次啟動完成後,依 docs/DEV-SETUP.md 「OAuth Client 註冊」一節,
|
||
# 手動建立 visionA OAuth client(MC 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 SSO(OIDC 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 issuer:visionA-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 即視為 alive;HTTP 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 UI(Razor MVC)
|
||
# 用來手動建 tenant + OAuth client(API 端目前無法注入 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 init(migrate 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 / OIDC(Phase 0.6 起強制走 OIDC)────
|
||
# OB5(2026-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 預設 user(OIDC 未綁定時的 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
|