This commit is contained in:
toom1996
2026-10-19 00:33:25 +08:00
parent 3860e731db
commit 5db90af73a
17 changed files with 853 additions and 5244 deletions

155
docker-compose.yml Normal file
View File

@ -0,0 +1,155 @@
# 生产部署:蓝绿双槽 + 常驻代理,支持零停机发布
#
# 服务器目录(必须同级):
# /opt/fashion/backend ← 本文件所在(后端仓,部署入口)
# /opt/fashion/test ← 前端仓(默认按 ../test 取,可用 .env 的 WEB_DIR 覆盖)
#
# 浏览器 ──▶ proxy(nginx:80,常驻不重建)
# ├─ /api ──▶ backend_a 或 backend_b
# ├─ / ──▶ web_a 或 web_b
# ├─ 18090(容器内) SSR 取数 → 当前活跃后端
# ├─ 127.0.0.1:18091 前端构建期取 SSG
# └─ 127.0.0.1:18092 管理后台(SSH 隧道)
#
# 首次部署:cp .env.example .env → 填密钥 → ./init-db.sh <sql> → ./deploy.sh all
# 日常发布:./deploy.sh(只更新后端) / ./deploy.sh all / ./deploy.sh --rollback
#
# a/b 两个槽共用同一份镜像标签(RELEASE=时间戳-提交号,不可变),
# deploy.sh 只重建空闲槽,换完 nginx -s reload 即切换。
x-backend-base: &backend-base
image: fashion-backend:${RELEASE:-latest}
restart: unless-stopped
environment:
SERVER_PORT: 8090
SSG_PORT: 8091
BACKSTAGE_PORT: 8092
GIN_MODE: release
HASHID_SECRET: ${HASHID_SECRET:?HASHID_SECRET 未设置:复制 .env.example 为 .env 并填写}
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET 未设置}
SSG_TOKEN: ${SSG_TOKEN:-}
CORS_ALLOW_ORIGINS: ${CORS_ALLOW_ORIGINS:-https://xisoa.com,https://www.xisoa.com}
RATE_LIMIT_ENABLED: ${RATE_LIMIT_ENABLED:-true}
RATE_LIMIT_RPS: ${RATE_LIMIT_RPS:-20}
RATE_LIMIT_BURST: ${RATE_LIMIT_BURST:-40}
DB_HOST: db
DB_PORT: 5432
DB_USER: ${DB_USER:-fashion}
DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD 未设置}
DB_NAME: ${DB_NAME:-fashion}
INGEST_SECRET: ${INGEST_SECRET:?INGEST_SECRET 未设置}
INGEST_TTL: ${INGEST_TTL:-300}
INGEST_DOWNLOAD_PROXY: ${INGEST_DOWNLOAD_PROXY:-}
INGEST_IMAGE_CONCURRENCY: ${INGEST_IMAGE_CONCURRENCY:-4}
S4_ENABLED: ${S4_ENABLED:-true}
S4_AK: ${S4_AK:?S4_AK 未设置}
S4_SK: ${S4_SK:?S4_SK 未设置}
S4_BUCKET: ${S4_BUCKET:-toomstudio}
S4_ENDPOINT: ${S4_ENDPOINT:-https://s3.bitiful.net}
S4_REGION: ${S4_REGION:-cn-east-1}
S4_BASE_URL: ${S4_BASE_URL:-}
VISION_ENABLED: ${VISION_ENABLED:-false}
VISION_ENDPOINT: ${VISION_ENDPOINT:-http://127.0.0.1:8900}
UPLOAD_DIR: /app/uploads
volumes:
- uploads:/app/uploads
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8090/api/health"]
interval: 10s
timeout: 5s
retries: 6
start_period: 15s
x-web-base: &web-base
image: fashion-web:${RELEASE:-latest}
restart: unless-stopped
environment:
# 容器内的 localhost 是它自己,SSR 取数统一走代理(永远指向活跃后端槽)
BASE_API_SSR: http://proxy:18090
healthcheck:
test: ["CMD", "nc", "-z", "127.0.0.1", "3000"]
interval: 10s
timeout: 3s
retries: 5
start_period: 15s
services:
# ---- 数据库:PostgreSQL 16 + pgvector ----
db:
image: pgvector/pgvector:pg16
restart: unless-stopped
environment:
POSTGRES_USER: ${DB_USER:-fashion}
POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD 未设置}
POSTGRES_DB: ${DB_NAME:-fashion}
TZ: Asia/Shanghai
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-fashion} -d ${DB_NAME:-fashion}"]
interval: 10s
timeout: 5s
retries: 10
# ---- 常驻代理:整个发布过程中不重建,切换只靠 reload ----
proxy:
image: nginx:1.27-alpine
restart: unless-stopped
ports:
- "${HTTP_PORT:-80}:80"
- "127.0.0.1:18091:18091" # 前端构建期取 SSG
- "127.0.0.1:18092:18092" # 管理后台(SSH 隧道)
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
# 切换点放在 .deploy/(已被 gitignore),发布时改写它不会弄脏工作区
- ./.deploy/nginx:/etc/nginx/active:ro
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
interval: 15s
timeout: 3s
retries: 5
start_period: 5s
depends_on:
backend_a:
condition: service_healthy
web_a:
condition: service_started
# ---- 后端双槽 ----
backend_a:
<<: *backend-base
build:
context: .
args:
GOPROXY: ${GOPROXY:-https://goproxy.cn,direct}
backend_b:
<<: *backend-base
# ---- 前端双槽(只跑 Astro node,nginx 在 proxy 里)----
web_a:
<<: *web-base
build:
context: ${WEB_DIR:-../test}
args:
# 走宿主回环的代理 18091,由代理转发到活跃后端槽
BASE_API_SSG: http://127.0.0.1:18091
SSG_TOKEN: ${SSG_TOKEN:-}
network: host
web_b:
<<: *web-base
volumes:
db_data:
uploads: