feat(deploy): 容器化 + 一键独立部署 —— 5 服务镜像 + prod compose + 安装/备份/许可

此前应用服务只是本机裸二进制、无 Dockerfile、无法交付。本提交把项目从"只能在我笔记本
跑"变成"任何人一条命令起一整套",是「演示 / 拉投资 / 卖给客户独立部署」的可交付基础。

- Dockerfile ×5:gateway/dispatcher/mcp-go 多阶段(distroless static, 36–55MB);
  mcp-py(python-slim, 268MB);admin(node 构建→nginx 托管 SPA + 反代 /api,76MB)。
  Go 构建上下文为仓库根以兜住 replace ../sundynix-shared;.dockerignore 瘦上下文。
- docker-compose.prod.yml:应用 + 基建一把起;经服务名互连(顺带避开 localhost DNS 坑);
  基建端口不对宿主暴露;depends_on 健康检查保序(mcp-go 待 Milvus healthy);
  APP_ENV=production 强制密钥校验 + 锁 CORS + 管理员白名单。
- .env.example + DEPLOY.md:两类密钥分层(引导密钥走 .env,模型 key 控制台加密存库);
  首次管理员流程;安全建议。LICENSE 专有(授权模型可后定)。
- scripts/backup.sh + restore.sh:PG 逻辑备份 + 各数据卷快照 / 恢复。
- .gitignore 挡 .env*(密钥不入库)。

live:5 镜像全构建通过;独立 project 起整套 → nginx→gateway→NATS→dispatcher→mcp 全链路
注册+提任务 running→done,depends_on 保序生效。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-26 15:23:36 +08:00
parent 165ecb4ec6
commit a5fd251fc0
17 changed files with 545 additions and 0 deletions
+183
View File
@@ -0,0 +1,183 @@
# sundynix-agentix · 生产/独立部署编排(应用 + 基建一把拉起)
#
# cp .env.example .env # 然后按提示填密钥(见 DEPLOY.md)
# docker compose -f docker-compose.prod.yml up -d --build
#
# 设计:
# - 应用经服务名互连(postgres:5432 等),不依赖 127.0.0.1;基建端口不对宿主暴露(仅内网)。
# - 仅暴露 admin 控制台(:${HTTP_PORT}) 与 gateway(:${GATEWAY_PORT},供桌面端直连) + Jaeger UI(可选)。
# - 引导密钥从 .env 注入(SUNDYNIX_SECRET_KEY/JWT_SECRET/各库密码);模型 API key 仍在控制台配、加密存 PG。
# - APP_ENV=production → 强制校验密钥、锁 CORS、启用管理员白名单。
x-secret-key: &secret-key ${SUNDYNIX_SECRET_KEY:?需在 .env 设置(openssl rand -base64 32}
services:
# ---------- 应用层 ----------
gateway:
build: { context: ., dockerfile: sundynix-gateway/Dockerfile }
image: sundynix/gateway:${TAG:-latest}
restart: unless-stopped
environment:
APP_ENV: production
GATEWAY_ADDR: ":8080"
NATS_URL: nats://nats:4222
POSTGRES_DSN: postgres://sundynix:${POSTGRES_PASSWORD:?}@postgres:5432/sundynix?sslmode=disable
REDIS_ADDR: redis:6379
MINIO_ENDPOINT: minio:9000
MINIO_ACCESS_KEY: minioadmin # minio 仅 compose 内网可达,不对宿主暴露
MINIO_SECRET_KEY: minioadmin
SUNDYNIX_SECRET_KEY: *secret-key
JWT_SECRET: ${JWT_SECRET:?需在 .env 设置(openssl rand -base64 32}
ADMIN_USER_IDS: ${ADMIN_USER_IDS:-}
CORS_ALLOW_ORIGIN: ${CORS_ALLOW_ORIGIN:-*}
OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318
ports: ["${GATEWAY_PORT:-8080}:8080"]
depends_on:
postgres: { condition: service_healthy }
redis: { condition: service_started }
nats: { condition: service_started }
dispatcher:
build: { context: ., dockerfile: sundynix-dispatcher/Dockerfile }
image: sundynix/dispatcher:${TAG:-latest}
restart: unless-stopped
environment:
APP_ENV: production
NATS_URL: nats://nats:4222
SUNDYNIX_SECRET_KEY: *secret-key
OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318
DISPATCHER_CONCURRENCY: ${DISPATCHER_CONCURRENCY:-8}
TASK_TOKEN_BUDGET: ${TASK_TOKEN_BUDGET:-200000}
depends_on:
nats: { condition: service_started }
mcp-go:
build: { context: ., dockerfile: sundynix-mcp-go/Dockerfile }
image: sundynix/mcp-go:${TAG:-latest}
restart: unless-stopped
environment:
APP_ENV: production
NATS_URL: nats://nats:4222
POSTGRES_DSN: postgres://sundynix:${POSTGRES_PASSWORD:?}@postgres:5432/sundynix?sslmode=disable
REDIS_ADDR: redis:6379
MILVUS_ADDR: milvus:19530
NEO4J_URI: neo4j://neo4j:7687
NEO4J_USER: neo4j
NEO4J_PASS: ${NEO4J_PASSWORD:?}
SUNDYNIX_SECRET_KEY: *secret-key
OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318
depends_on:
nats: { condition: service_started }
milvus: { condition: service_healthy } # mcp-go 须在 Milvus 后起,否则工具 no responders
mcp-py:
build: { context: sundynix-mcp-py, dockerfile: Dockerfile }
image: sundynix/mcp-py:${TAG:-latest}
restart: unless-stopped
environment:
NATS_URL: nats://nats:4222
# secure_sandbox(代码隔离执行)需访问宿主 Docker 拉起一次性容器。
# 安全敏感:等于把宿主 Docker 暴露给该服务;不需要代码执行工具可注释掉这行。
volumes:
- /var/run/docker.sock:/var/run/docker.sock
depends_on:
nats: { condition: service_started }
admin:
build: { context: sundynix-admin, dockerfile: Dockerfile }
image: sundynix/admin:${TAG:-latest}
restart: unless-stopped
ports: ["${HTTP_PORT:-80}:80"] # 运维控制台 + 反代网关 API(一个 URL 访问整套)
depends_on:
gateway: { condition: service_started }
# ---------- 基础设施(端口不对宿主暴露,仅 compose 内网) ----------
nats:
image: nats:2-alpine
command: ["-c", "/etc/nats/nats-server.conf"]
restart: unless-stopped
volumes:
- ./deploy/nats/nats-server.conf:/etc/nats/nats-server.conf:ro
- nats_data:/data/jetstream
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: sundynix
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?}
POSTGRES_DB: sundynix
volumes: ["pg_data:/var/lib/postgresql/data"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U sundynix"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
restart: unless-stopped
volumes: ["redis_data:/data"]
jaeger:
image: jaegertracing/all-in-one:1.60
restart: unless-stopped
environment:
COLLECTOR_OTLP_ENABLED: "true"
ports: ["${JAEGER_UI_PORT:-16686}:16686"] # 链路追踪 UI(演示可看 span 瀑布图;不需要可去掉)
milvus-etcd:
image: quay.io/coreos/etcd:v3.5.14
restart: unless-stopped
environment:
ETCD_AUTO_COMPACTION_MODE: revision
ETCD_AUTO_COMPACTION_RETENTION: "1000"
ETCD_QUOTA_BACKEND_BYTES: "4294967296"
ETCD_SNAPSHOT_COUNT: "50000"
command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
volumes: ["milvus_etcd:/etcd"]
minio:
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
restart: unless-stopped
environment:
MINIO_ACCESS_KEY: minioadmin # 内网专用(不对宿主暴露端口),与 milvus/gateway 默认对齐
MINIO_SECRET_KEY: minioadmin
command: minio server /minio_data
volumes: ["minio_data:/minio_data"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
milvus:
image: milvusdb/milvus:v2.4.13
command: ["milvus", "run", "standalone"]
restart: unless-stopped
environment:
ETCD_ENDPOINTS: milvus-etcd:2379
MINIO_ADDRESS: minio:9000
volumes: ["milvus_data:/var/lib/milvus"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
interval: 15s
timeout: 10s
retries: 10
depends_on: [milvus-etcd, minio]
neo4j:
image: neo4j:5-community
restart: unless-stopped
environment:
NEO4J_AUTH: neo4j/${NEO4J_PASSWORD:?}
volumes: ["neo4j_data:/data"]
volumes:
nats_data:
pg_data:
redis_data:
milvus_etcd:
minio_data:
milvus_data:
neo4j_data: