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
+11
View File
@@ -0,0 +1,11 @@
.git
**/node_modules
**/.venv
**/dist
**/build
sundynix-desktop/build
backups
*.log
/tmp
**/*_test.go
.env
+47
View File
@@ -0,0 +1,47 @@
# sundynix-agentix 部署配置模板 —— 复制为 .env 后填写(docker compose -f docker-compose.prod.yml 读取)
#
# cp .env.example .env
# # 按下方注释生成/填写各项,然后:
# docker compose -f docker-compose.prod.yml up -d --build
#
# 说明:这里只放「引导密钥/基建凭据」。LLM 模型 API key 不在这里 —— 部署起来后登录控制台配置、
# 由平台 AES 加密存库(加密用的就是下面的 SUNDYNIX_SECRET_KEY)。
# ── 必填:引导密钥(生成命令见各行) ─────────────────────────────
# 主加密密钥:用于加密控制台里配置的模型 API key。三服务共用同一个,丢了=已存的密钥都解不开。
# 生成:openssl rand -base64 32
SUNDYNIX_SECRET_KEY=
# 登录令牌签名密钥(JWT)。
# 生成:openssl rand -base64 32
JWT_SECRET=
# 数据库密码(PostgreSQL,平台主库)。
# 生成:openssl rand -base64 24 (勿用特殊字符 @ : / 以免污染 DSN)
POSTGRES_PASSWORD=
# 图数据库密码(Neo4j,知识图谱)。
NEO4J_PASSWORD=
# ── 必填:管理员 ─────────────────────────────────────────────
# 管理员用户 ID 白名单(逗号分隔)。生产模式下只有这些用户能进 /admin 控制台。
# 首次部署:先留空起服务 → 注册第一个账号 → 从返回的 user id 填进来 → 重启 gateway。
ADMIN_USER_IDS=
# ── 选填:网络/端口 ─────────────────────────────────────────
# 控制台对外端口(默认 80)。
HTTP_PORT=80
# 网关对外端口(供桌面端直连;纯 Web 用可不暴露)。
GATEWAY_PORT=8080
# 允许的前端来源(生产建议设具体域名,如 https://app.example.com;缺省 * 仅供内网/演示)。
CORS_ALLOW_ORIGIN=*
# Jaeger 链路追踪 UI 端口。
JAEGER_UI_PORT=16686
# ── 选填:调优 ───────────────────────────────────────────────
# 单调度副本并发任务数。
DISPATCHER_CONCURRENCY=8
# 单任务 token 预算上限(防失控成本)。
TASK_TOKEN_BUDGET=200000
# 镜像标签。
TAG=latest
+6
View File
@@ -28,3 +28,9 @@ data/
# Claude Code 本地机器配置(launch.json 可共享,settings.local 不提交) # Claude Code 本地机器配置(launch.json 可共享,settings.local 不提交)
.claude/settings.local.json .claude/settings.local.json
# 部署密钥(绝不入库)
.env
.env.*
!.env.example
backups/
+69
View File
@@ -0,0 +1,69 @@
# 部署指南(独立部署 / 单机演示)
一条命令拉起整套 sundynix-agentix(应用 5 服务 + 基建)。适用于**演示**与**客户独立部署**。
集群高可用(多副本 / NATS 集群 / 数据库主从)是用户量上来后的事,不在本指南范围。
## 前置
- Docker 24+ 与 Docker Compose v2
- 约 8GB 内存、10GB 磁盘(Milvus/Neo4j 较吃资源)
## 三步上手
```bash
# 1) 生成配置
cp .env.example .env
# 2) 填密钥(必填四项 + 管理员)
# 主加密密钥 / JWT 密钥:
openssl rand -base64 32 # 填入 SUNDYNIX_SECRET_KEY
openssl rand -base64 32 # 填入 JWT_SECRET
# 数据库密码:
openssl rand -base64 24 # 填入 POSTGRES_PASSWORD(勿含 @ : /
openssl rand -base64 24 # 填入 NEO4J_PASSWORD
# ADMIN_USER_IDS 先留空(见下「首次管理员」)
# 3) 起服务(首次会构建镜像,约几分钟)
docker compose -f docker-compose.prod.yml up -d --build
```
起来后:
- 控制台: http://<服务器IP>:80
- 网关 API http://<服务器IP>:8080 (桌面端连这里)
- 链路追踪 UI(可选): http://<服务器IP>:16686
## 首次管理员
生产模式下 `/admin``ADMIN_USER_IDS` 白名单可进。首次:
1. 打开控制台注册第一个账号;
2. 取该账号的用户 id(注册响应里有,或 `GET /api/v1/auth/me`);
3. 填进 `.env``ADMIN_USER_IDS``docker compose -f docker-compose.prod.yml up -d gateway` 重启网关。
## 配置模型(部署后在控制台做)
LLM 模型 API key **不放 .env**,登录控制台 →「模型管理」配置:
- 对话模型(chat):在线 APIOpenAI 兼容,如 DeepSeek)或本地 vLLM/Ollama
- 向量模型(embedding):RAG 用
平台会用 `SUNDYNIX_SECRET_KEY` 把这些 key AES 加密后存库,不落明文。
## 两类密钥(重要)
| 类别 | 放哪 | 例子 |
|---|---|---|
| 引导密钥 / 基建凭据 | `.env`(部署时填一次) | `SUNDYNIX_SECRET_KEY``JWT_SECRET`、库密码 |
| 业务模型 key | 控制台(运行时配,加密存库) | DeepSeek / Qwen / embedding 的 api_key |
`SUNDYNIX_SECRET_KEY` 是前者加密后者的主密钥,**务必备份且不可泄露**;丢失=已存模型 key 全部失效。
## 备份 / 恢复
`scripts/backup.sh``scripts/restore.sh`(备份 PG / Neo4j / Milvus / MinIO 数据卷)。
建议 cron 每日跑 `backup.sh`,产物存异机。
## 安全建议(对外开放时)
- 生产把 `CORS_ALLOW_ORIGIN` 设为具体域名,别留 `*`
- 基建端口(PG/Redis/NATS/Milvus/Neo4j/MinIO)默认**不对宿主暴露**,仅 compose 内网;勿手动开放。
- 对外建议在 admin 容器前再加一层 TLS 反代(Caddy/Nginx),或用云 LB 终止 HTTPS。
- `SUNDYNIX_SECRET_KEY`/`JWT_SECRET``openssl rand` 生成的强随机值,切勿用示例值。
## 运维常用
```bash
docker compose -f docker-compose.prod.yml ps # 状态
docker compose -f docker-compose.prod.yml logs -f gateway
docker compose -f docker-compose.prod.yml down # 停(保留数据卷)
docker compose -f docker-compose.prod.yml up -d --build gateway # 改配置后单独重启
```
+20
View File
@@ -0,0 +1,20 @@
专有软件许可 / Proprietary License
Copyright (c) 2026 sundynix-agentix. 保留所有权利 / All Rights Reserved.
本软件及其源代码、文档、构建产物(统称“本软件”)为版权所有者的专有财产。
未经版权所有者事先书面授权,任何个人或实体不得复制、修改、合并、发布、分发、
再许可、出售本软件,或将其用于商业或生产用途。
获授权方(如签署商业许可/独立部署协议者)的使用范围、期限与限制,以双方另行
签署的书面协议为准;本文件不授予任何此类协议之外的权利。
本软件按“现状”提供,不附任何明示或默示担保,包括但不限于适销性、特定用途
适用性与不侵权的担保。版权所有者不对任何索赔、损害或其他责任负责。
---
This software is proprietary. No rights are granted except under a separate
written commercial/deployment agreement with the copyright holder.
注:授权模型(专有 / 源码可见 BSL / 双授权等)为商业决策,可按需替换本文件。
+12
View File
@@ -24,6 +24,18 @@
## 快速开始 ## 快速开始
### 生产 / 独立部署 — 一条命令拉起整套(应用 + 基建)
```bash
cp .env.example .env # 按提示填密钥(openssl rand -base64 32
docker compose -f docker-compose.prod.yml up -d --build
# 控制台 http://<IP>:80 网关 http://<IP>:8080 链路 UI http://<IP>:16686
```
应用全部容器化(gateway / dispatcher / mcp-go / mcp-py / admin-nginx),经服务名互连,
基建端口不对外;模型 API key 部署后在控制台配(加密存库)。详见 **[DEPLOY.md](DEPLOY.md)**、
备份恢复见 `scripts/backup.sh`
### 无 Docker — 一键验证任务流(推荐先跑这个) ### 无 Docker — 一键验证任务流(推荐先跑这个)
```bash ```bash
+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:
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# 备份 sundynix-agentix 全部有状态数据(PG / Neo4j / Milvus / MinIO / NATS-JetStream)。
# 用法:scripts/backup.sh [输出目录] 默认 ./backups/<时间戳>
# 建议 cron 每日跑,产物拷到异机。
set -euo pipefail
COMPOSE="docker compose -f docker-compose.prod.yml"
TS="$(date +%Y%m%d-%H%M%S)"
OUT="${1:-./backups/$TS}"
mkdir -p "$OUT"
echo "[backup] → $OUT"
# PostgreSQL:逻辑备份(可跨版本恢复)。
echo "[backup] postgres…"
$COMPOSE exec -T postgres pg_dump -U sundynix sundynix | gzip > "$OUT/postgres.sql.gz"
# Neo4j:在线 dump(社区版用 cypher-shell 导出关系;这里用 APOC/dump 兼容写法的简化版——
# 社区版无 neo4j-admin 在线 dump,改为停服卷拷或用 cypher 导出。此处导出全图为 cypher。)
echo "[backup] neo4j…"
$COMPOSE exec -T neo4j sh -lc 'command -v neo4j-admin >/dev/null 2>&1 && neo4j-admin database dump neo4j --to-stdout 2>/dev/null' \
> "$OUT/neo4j.dump" 2>/dev/null || echo "[backup] neo4j 在线 dump 不可用(社区版),改用卷快照见下"
# 数据卷快照(Milvus / MinIO / Neo4j 兜底 / JetStream):用临时容器打 tar。
backup_volume () { # $1=卷名 $2=输出文件
local vol; vol="$($COMPOSE config --volumes | grep -E "^${1}\$" || true)"
local full="$(docker volume ls --format '{{.Name}}' | grep -E "_${1}\$" | head -1)"
[ -z "$full" ] && { echo "[backup] 卷 $1 未找到,跳过"; return; }
echo "[backup] volume $full"
docker run --rm -v "$full":/data -v "$(cd "$OUT" && pwd)":/out alpine \
sh -c "cd /data && tar czf /out/$2 ."
}
backup_volume milvus_data milvus_data.tar.gz
backup_volume minio_data minio_data.tar.gz
backup_volume neo4j_data neo4j_data.tar.gz
backup_volume nats_data nats_data.tar.gz
echo "[backup] 完成。务必另存 .env(含 SUNDYNIX_SECRET_KEY,丢了模型密钥全废):cp .env $OUT/env.bak"
echo "$OUT"
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# 从 backup.sh 的产物恢复。用法:scripts/restore.sh <备份目录>
# 会停服 → 恢复 PG(逻辑)+ 各数据卷(覆盖)→ 重启。务必先确认目录正确,操作不可逆。
set -euo pipefail
COMPOSE="docker compose -f docker-compose.prod.yml"
DIR="${1:?用法: scripts/restore.sh <备份目录>}"
[ -d "$DIR" ] || { echo "目录不存在: $DIR"; exit 1; }
echo "[restore] 从 $DIR 恢复,将覆盖现有数据。Ctrl-C 取消,5s 后继续…"; sleep 5
echo "[restore] 停应用(保留基建跑着以便恢复 PG)…"
$COMPOSE stop gateway dispatcher mcp-go mcp-py admin || true
# PostgreSQL:逻辑恢复(先清库再灌)。
if [ -f "$DIR/postgres.sql.gz" ]; then
echo "[restore] postgres…"
$COMPOSE up -d postgres; sleep 5
$COMPOSE exec -T postgres psql -U sundynix -d sundynix -c \
"DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
gunzip -c "$DIR/postgres.sql.gz" | $COMPOSE exec -T postgres psql -U sundynix -d sundynix
fi
# 数据卷恢复(停服 → 清空卷 → 解 tar)。
restore_volume () { # $1=卷后缀 $2=tar 文件
[ -f "$DIR/$2" ] || { echo "[restore] 无 $2,跳过"; return; }
local full; full="$(docker volume ls --format '{{.Name}}' | grep -E "_${1}\$" | head -1)"
[ -z "$full" ] && { echo "[restore] 卷 $1 未找到,跳过"; return; }
echo "[restore] volume $full$2"
docker run --rm -v "$full":/data -v "$(cd "$DIR" && pwd)":/in alpine \
sh -c "rm -rf /data/* && cd /data && tar xzf /in/$2"
}
echo "[restore] 停基建以恢复卷…"; $COMPOSE down
restore_volume milvus_data milvus_data.tar.gz
restore_volume minio_data minio_data.tar.gz
restore_volume neo4j_data neo4j_data.tar.gz
restore_volume nats_data nats_data.tar.gz
echo "[restore] 重启全部…"; $COMPOSE -f docker-compose.prod.yml up -d
echo "[restore] 完成。若恢复到新机,记得把备份里的 env.bak 还原为 .env。"
+3
View File
@@ -0,0 +1,3 @@
node_modules
dist
.env
+15
View File
@@ -0,0 +1,15 @@
# 运维控制台:node 构建 SPA → nginx 托管 + 反代网关 API(一个 URL 访问整套)。
# docker build -t sundynix/admin sundynix-admin
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# VITE_GATEWAY 留空 → 前端走相对路径 /api/...,由 nginx 反代到 gateway(同源、免跨域)。
ENV VITE_GATEWAY=""
RUN npm run build
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
+30
View File
@@ -0,0 +1,30 @@
# 运维控制台静态托管 + 反代网关 API(一个 URL 同时给前端与 /api)。
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# SPA 路由:找不到文件回退 index.html(前端路由接管)。
location / {
try_files $uri $uri/ /index.html;
}
# 网关 API:反代到 compose 网络内的 gateway 服务。
location /api/ {
proxy_pass http://gateway:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# SSE(token 流 / 执行轨迹):关闭缓冲 + 长超时,否则流式会被攒住/断开。
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
# 健康/指标直通(运维探活)。
location = /healthz { proxy_pass http://gateway:8080; }
location = /readyz { proxy_pass http://gateway:8080; }
}
+18
View File
@@ -0,0 +1,18 @@
# 第 4 层 编排调度。多阶段构建(上下文须为仓库根,因 replace ../sundynix-shared):
# docker build -f sundynix-dispatcher/Dockerfile -t sundynix/dispatcher .
FROM golang:1.25-alpine AS build
WORKDIR /src
COPY sundynix-shared/go.mod sundynix-shared/go.sum ./sundynix-shared/
COPY sundynix-dispatcher/go.mod sundynix-dispatcher/go.sum ./sundynix-dispatcher/
WORKDIR /src/sundynix-dispatcher
ENV GOWORK=off GOFLAGS=-mod=mod
RUN go mod download
WORKDIR /src
COPY sundynix-shared/ ./sundynix-shared/
COPY sundynix-dispatcher/ ./sundynix-dispatcher/
WORKDIR /src/sundynix-dispatcher
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/dispatcher ./cmd/dispatcher
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/dispatcher /dispatcher
ENTRYPOINT ["/dispatcher"]
+22
View File
@@ -0,0 +1,22 @@
# 第 2 层 业务网关。多阶段构建:编译静态二进制 → distroless 最小运行镜像。
# 构建上下文须为「仓库根」(因 go.mod 用 replace ../sundynix-shared 引本地共享模块):
# docker build -f sundynix-gateway/Dockerfile -t sundynix/gateway .
FROM golang:1.25-alpine AS build
WORKDIR /src
# 先拷依赖清单分层缓存(命中则跳过重下)
COPY sundynix-shared/go.mod sundynix-shared/go.sum ./sundynix-shared/
COPY sundynix-gateway/go.mod sundynix-gateway/go.sum ./sundynix-gateway/
WORKDIR /src/sundynix-gateway
ENV GOWORK=off GOFLAGS=-mod=mod
RUN go mod download
# 再拷源码编译
WORKDIR /src
COPY sundynix-shared/ ./sundynix-shared/
COPY sundynix-gateway/ ./sundynix-gateway/
WORKDIR /src/sundynix-gateway
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/gateway ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/gateway /gateway
EXPOSE 8080
ENTRYPOINT ["/gateway"]
+18
View File
@@ -0,0 +1,18 @@
# 第 5 层 Go I/O 型 MCP 工具(检索/入库/报告渲染等)。多阶段构建(上下文须为仓库根):
# docker build -f sundynix-mcp-go/Dockerfile -t sundynix/mcp-go .
FROM golang:1.25-alpine AS build
WORKDIR /src
COPY sundynix-shared/go.mod sundynix-shared/go.sum ./sundynix-shared/
COPY sundynix-mcp-go/go.mod sundynix-mcp-go/go.sum ./sundynix-mcp-go/
WORKDIR /src/sundynix-mcp-go
ENV GOWORK=off GOFLAGS=-mod=mod
RUN go mod download
WORKDIR /src
COPY sundynix-shared/ ./sundynix-shared/
COPY sundynix-mcp-go/ ./sundynix-mcp-go/
WORKDIR /src/sundynix-mcp-go
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/mcp-go ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/mcp-go /mcp-go
ENTRYPOINT ["/mcp-go"]
+4
View File
@@ -0,0 +1,4 @@
.venv
__pycache__
**/__pycache__
*.pyc
+10
View File
@@ -0,0 +1,10 @@
# 第 5 层 Python 算法型 MCP 工具(代码执行/文档解析等)。
# docker build -f sundynix-mcp-py/Dockerfile -t sundynix/mcp-py sundynix-mcp-py
# 注:secure_sandbox(代码隔离执行)需访问宿主 Docker —— compose 里挂 /var/run/docker.sock。
FROM python:3.11-slim
WORKDIR /app
# 先装依赖(利用层缓存)
COPY pyproject.toml ./
COPY src/ ./src/
RUN pip install --no-cache-dir . && rm -rf /root/.cache
ENTRYPOINT ["python", "-m", "sundynix_mcp_py.main"]