feat(deploy): add Dockerfile, saas-env.example, nginx config, and production deployment guide

Multi-stage Docker build for zclaw-saas with dependency caching,
environment variable template with security defaults, Nginx reverse
proxy with SSE/WebSocket support and HTTPS, and comprehensive
Chinese-language production deployment documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-31 10:26:30 +08:00
parent 8e6abc91e1
commit d0ae7d2770
4 changed files with 680 additions and 0 deletions

106
Dockerfile Normal file
View File

@@ -0,0 +1,106 @@
# ============================================================
# ZCLAW SaaS Backend - Multi-stage Docker Build
# ============================================================
# Build: docker build -t zclaw-saas .
# Run: docker run --env-file saas-env.example zclaw-saas
# ============================================================
#
# .dockerignore recommended contents:
# target/
# node_modules/
# desktop/
# admin/
# admin-v2/
# docs/
# .git/
# .claude/
# *.md
# *.pen
# plans/
# dist/
# pencil-new.pen
# ============================================================
# ---- Stage 1: Build ----
FROM rust:1.85-bookworm AS builder
WORKDIR /usr/src/zclaw
# Cache dependency builds by copying manifests first
COPY Cargo.toml Cargo.lock ./
# Create dummy lib.rs files so cargo can resolve the workspace
RUN mkdir -p crates/zclaw-types/src && echo "" > crates/zclaw-types/src/lib.rs
RUN mkdir -p crates/zclaw-memory/src && echo "" > crates/zclaw-memory/src/lib.rs
RUN mkdir -p crates/zclaw-runtime/src && echo "" > crates/zclaw-runtime/src/lib.rs
RUN mkdir -p crates/zclaw-kernel/src && echo "" > crates/zclaw-kernel/src/lib.rs
RUN mkdir -p crates/zclaw-skills/src && echo "" > crates/zclaw-skills/src/lib.rs
RUN mkdir -p crates/zclaw-hands/src && echo "" > crates/zclaw-hands/src/lib.rs
RUN mkdir -p crates/zclaw-protocols/src && echo "" > crates/zclaw-protocols/src/lib.rs
RUN mkdir -p crates/zclaw-pipeline/src && echo "" > crates/zclaw-pipeline/src/lib.rs
RUN mkdir -p crates/zclaw-growth/src && echo "" > crates/zclaw-growth/src/lib.rs
RUN mkdir -p crates/zclaw-saas/src && echo "fn main() {}" > crates/zclaw-saas/src/main.rs
RUN mkdir -p desktop/src-tauri/src && echo "" > desktop/src-tauri/src/lib.rs
# Copy all crate Cargo.toml files for dependency resolution
COPY crates/zclaw-types/Cargo.toml crates/zclaw-types/Cargo.toml
COPY crates/zclaw-memory/Cargo.toml crates/zclaw-memory/Cargo.toml
COPY crates/zclaw-runtime/Cargo.toml crates/zclaw-runtime/Cargo.toml
COPY crates/zclaw-kernel/Cargo.toml crates/zclaw-kernel/Cargo.toml
COPY crates/zclaw-skills/Cargo.toml crates/zclaw-skills/Cargo.toml
COPY crates/zclaw-hands/Cargo.toml crates/zclaw-hands/Cargo.toml
COPY crates/zclaw-protocols/Cargo.toml crates/zclaw-protocols/Cargo.toml
COPY crates/zclaw-pipeline/Cargo.toml crates/zclaw-pipeline/Cargo.toml
COPY crates/zclaw-growth/Cargo.toml crates/zclaw-growth/Cargo.toml
COPY crates/zclaw-saas/Cargo.toml crates/zclaw-saas/Cargo.toml
COPY desktop/src-tauri/Cargo.toml desktop/src-tauri/Cargo.toml
# Build dependencies only (cached layer)
RUN cargo build --release -p zclaw-saas 2>/dev/null || true
# Now copy the actual source code
COPY crates/ crates/
COPY desktop/src-tauri/src/ desktop/src-tauri/src/
# Touch source files to invalidate cache after dependency layer
RUN find crates/zclaw-saas/src -type f -exec touch {} +
# Build the final binary
RUN cargo build --release -p zclaw-saas
# ---- Stage 2: Runtime ----
FROM debian:bookworm-slim
# Install runtime dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates \
curl \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user for security
RUN groupadd --gid 1000 zclaw && \
useradd --uid 1000 --gid zclaw --shell /bin/bash --create-home zclaw
WORKDIR /app
# Copy binary from builder
COPY --from=builder /usr/src/zclaw/target/release/zclaw-saas ./zclaw-saas
# Copy default config (can be overridden by env vars)
COPY saas-config.toml ./saas-config.toml
# Set ownership
RUN chown -R zclaw:zclaw /app
# Switch to non-root user
USER zclaw
# Expose SaaS backend port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
ENTRYPOINT ["./zclaw-saas"]

110
deploy/nginx.conf Normal file
View File

@@ -0,0 +1,110 @@
# ============================================================
# ZCLAW SaaS Backend - Nginx Reverse Proxy Configuration
# ============================================================
# Prerequisites:
# - SSL certificate (e.g., Let's Encrypt certbot)
# - Nginx installed (apt install nginx)
#
# Installation:
# sudo cp deploy/nginx.conf /etc/nginx/sites-available/zclaw-saas
# sudo ln -s /etc/nginx/sites-available/zclaw-saas /etc/nginx/sites-enabled/
# sudo nginx -t && sudo systemctl reload nginx
#
# Replace placeholders:
# <YOUR_DOMAIN> - e.g., api.zclaw.com
# <CERT_PATH> - e.g., /etc/letsencrypt/live/api.zclaw.com/fullchain.pem
# <KEY_PATH> - e.g., /etc/letsencrypt/live/api.zclaw.com/privkey.pem
# ============================================================
# ---- HTTP -> HTTPS Redirect ----
server {
listen 80;
listen [::]:80;
server_name <YOUR_DOMAIN>;
# Let's Encrypt challenge (keep if using certbot)
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# ---- HTTPS Server ----
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name <YOUR_DOMAIN>;
# ---- SSL Configuration ----
ssl_certificate <CERT_PATH>;
ssl_certificate_key <KEY_PATH>;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# ---- Security Headers ----
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
# ---- Gzip Compression ----
gzip on;
gzip_types
application/json
application/javascript
text/plain
text/css
text/xml
text/javascript;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
# ---- Request Size Limit ----
client_max_body_size 10m;
# ---- Proxy to ZCLAW SaaS Backend ----
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
# ---- WebSocket / SSE Support ----
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# ---- Standard Proxy Headers ----
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# ---- SSE Streaming Support ----
# Disable buffering for Server-Sent Events
proxy_buffering off;
proxy_cache off;
# Long timeout for streaming responses (5 minutes)
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# Disable request buffering for large payloads
proxy_request_buffering off;
}
# ---- Health Check Endpoint (no logging) ----
location = /health {
proxy_pass http://127.0.0.1:8080/health;
access_log off;
}
# ---- Logging ----
access_log /var/log/nginx/zclaw-saas-access.log;
error_log /var/log/nginx/zclaw-saas-error.log warn;
}

View File

@@ -0,0 +1,429 @@
# ZCLAW SaaS 生产环境部署指南
本指南覆盖 ZCLAW SaaS 后端的生产环境部署,包括 Docker 容器化、Nginx 反向代理、HTTPS 配置、安全加固和备份策略。
---
## 1. 环境要求
| 组件 | 最低版本 | 说明 |
|------------|-----------|------------------------------|
| Docker | 24.0+ | 容器运行时 |
| Docker Compose | v2.0+ | 容器编排 |
| Nginx | 1.24+ | 反向代理 + TLS 终止 |
| PostgreSQL | 16+ | 数据库Docker 内置或外部) |
| RAM | 2 GB+ | 最低内存 |
| Disk | 20 GB+ | 数据库 + 日志空间 |
### 端口规划
| 端口 | 用途 | 暴露方式 |
|-------|------------------------|--------------|
| 80 | HTTP (Nginx) | 外部 -> Nginx |
| 443 | HTTPS (Nginx) | 外部 -> Nginx |
| 8080 | SaaS 后端 (Axum) | 仅本地回环 |
| 5432 | PostgreSQL | 仅 Docker 网络 |
---
## 2. 快速启动 (Docker Compose)
### 2.1 克隆仓库并配置环境变量
```bash
git clone https://github.com/zclaw/zclaw.git
cd zclaw
# 从模板创建环境变量文件
cp saas-env.example .env
```
### 2.2 编辑 `.env` 填入真实值
```bash
# 必须修改的变量:
# - POSTGRES_PASSWORD 数据库密码
# - ZCLAW_DATABASE_URL 连接字符串(密码需与上面一致)
# - ZCLAW_SAAS_JWT_SECRET JWT 密钥openssl rand -hex 32
# - ZCLAW_TOTP_ENCRYPTION_KEY 加密密钥openssl rand -hex 32
# - ZCLAW_ADMIN_PASSWORD 管理员密码
# 生成随机密钥
export JWT_SECRET=$(openssl rand -hex 32)
export TOTP_KEY=$(openssl rand -hex 32)
# 使用 sed 替换(或手动编辑)
sed -i "s/请使用openssl_rand_hex_32生成/$JWT_SECRET/" .env
sed -i "0,/请使用openssl_rand_hex_32生成/s//$TOTP_KEY/" .env
```
### 2.3 启动服务
```bash
# 构建并启动(后台运行)
docker compose up -d --build
# 查看日志
docker compose logs -f saas
# 验证服务健康
curl http://localhost:8080/health
```
### 2.4 常用运维命令
```bash
# 停止服务
docker compose down
# 重启 SaaS 后端(不影响数据库)
docker compose restart saas
# 查看数据库状态
docker compose exec postgres pg_isready -U zclaw
# 进入数据库
docker compose exec postgres psql -U zclaw -d zclaw_saas
# 查看资源使用
docker stats zclaw-saas zclaw-postgres
```
---
## 3. 环境变量配置
### 3.1 核心变量
| 变量 | 必填 | 说明 |
|----------------------------|------|------------------------------------------|
| `POSTGRES_USER` | 是 | 数据库用户名 |
| `POSTGRES_PASSWORD` | 是 | 数据库密码 |
| `POSTGRES_DB` | 是 | 数据库名称 |
| `ZCLAW_DATABASE_URL` | 是 | 完整数据库连接 URL |
| `ZCLAW_SAAS_JWT_SECRET` | 是 | JWT 签名密钥(>= 32 字符随机字符串) |
| `ZCLAW_TOTP_ENCRYPTION_KEY`| 是 | TOTP/API Key 加密密钥64 字符 hex |
| `ZCLAW_ADMIN_USERNAME` | 否 | 初始管理员用户名(默认 admin |
| `ZCLAW_ADMIN_PASSWORD` | 否 | 初始管理员密码 |
| `ZCLAW_SAAS_DEV` | 否 | 开发模式标志(生产环境必须 false 或不设置) |
### 3.2 连接 URL 格式
```
postgres://<用户名>:<密码>@<主机>:<端口>/<数据库名>
```
Docker 内部网络使用主机名 `postgres`
```
ZCLAW_DATABASE_URL=postgres://zclaw:your_password@postgres:5432/zclaw_saas
```
外部 PostgreSQL 使用实际 IP/域名:
```
ZCLAW_DATABASE_URL=postgres://zclaw:your_password@10.0.0.5:5432/zclaw_saas
```
### 3.3 saas-config.toml 配置
`saas-config.toml` 提供默认配置,环境变量优先级更高:
```toml
[server]
host = "0.0.0.0"
port = 8080
cors_origins = ["https://your-domain.com"]
[database]
url = "postgres://zclaw:${DB_PASSWORD}@localhost:5432/zclaw"
[auth]
jwt_expiration_hours = 24
totp_issuer = "ZCLAW SaaS"
[rate_limit]
requests_per_minute = 60
burst = 10
```
配置支持 `${ENV_VAR}` 环境变量插值。
---
## 4. Nginx 反向代理 + HTTPS
### 4.1 安装 Nginx
```bash
sudo apt update && sudo apt install -y nginx
```
### 4.2 获取 SSL 证书 (Let's Encrypt)
```bash
# 安装 certbot
sudo apt install -y certbot python3-certbot-nginx
# 获取证书(替换为你的域名和邮箱)
sudo certbot --nginx -d api.yourdomain.com -m admin@yourdomain.com --agree-tos
```
### 4.3 配置 Nginx
```bash
# 复制配置模板
sudo cp deploy/nginx.conf /etc/nginx/sites-available/zclaw-saas
# 替换占位符
sudo sed -i 's/<YOUR_DOMAIN>/api.yourdomain.com/g' /etc/nginx/sites-available/zclaw-saas
sudo sed -i 's|<CERT_PATH>|/etc/letsencrypt/live/api.yourdomain.com/fullchain.pem|g' /etc/nginx/sites-available/zclaw-saas
sudo sed -i 's|<KEY_PATH>|/etc/letsencrypt/live/api.yourdomain.com/privkey.pem|g' /etc/nginx/sites-available/zclaw-saas
# 启用站点
sudo ln -sf /etc/nginx/sites-available/zclaw-saas /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
# 验证配置
sudo nginx -t
# 重载 Nginx
sudo systemctl reload nginx
```
### 4.4 自动续期
Let's Encrypt 证书有效期为 90 天certbot 会自动设置定时续期:
```bash
# 验证续期定时器
sudo systemctl status certbot.timer
# 手动测试续期
sudo certbot renew --dry-run
```
### 4.5 SSE / WebSocket 注意事项
Nginx 配置中已包含 SSE 流式响应支持:
- `proxy_buffering off` - 禁用缓冲,确保事件实时推送
- `proxy_read_timeout 300s` - 5 分钟超时,支持长连接
- `Upgrade` / `Connection` 头 - 支持 WebSocket 升级
如果需要更长的流式响应时间,调大 `proxy_read_timeout`
---
## 5. 安全清单
### 5.1 部署前必检
- [ ] 所有密码/密钥已替换为强随机值(非模板默认值)
- [ ] `ZCLAW_SAAS_DEV` 未设置或设为 `false`
- [ ] `cors_origins` 仅包含实际域名(不含 localhost
- [ ] PostgreSQL 端口 (5432) 未暴露到公网
- [ ] SaaS 后端端口 (8080) 仅绑定 127.0.0.1
- [ ] SSL 证书已配置且有效
- [ ] Nginx 安全头已启用X-Frame-Options, HSTS 等)
- [ ] `.env` 文件权限设为 600`chmod 600 .env`
### 5.2 Cookie 安全
| 属性 | 开发环境 | 生产环境 |
|---------------|---------|---------|
| `Secure` | false | true |
| `HttpOnly` | true | true |
| `SameSite` | Strict | Strict |
| Path | /api | /api |
### 5.3 Rate Limiting
内置 API 请求限流:
- `/api/auth/login` - 5 次/分钟/IP防暴力破解
- `/api/auth/register` - 3 次/小时/IP防刷注册
- 公共端点 - 20 次/分钟/IP防滥用
### 5.4 防火墙配置
```bash
# 仅开放必要端口
sudo ufw default deny incoming
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
```
### 5.5 日志审计
生产环境建议将日志写入文件并配置日志轮转:
```bash
# Nginx 日志轮转(通常已内置)
sudo vim /etc/logrotate.d/nginx
```
---
## 6. 备份策略
### 6.1 数据库备份
```bash
# 手动备份
docker compose exec postgres pg_dump -U zclaw zclaw_saas > backup_$(date +%Y%m%d_%H%M%S).sql
# 自动每日备份crontab
# 每天凌晨 3 点备份
echo "0 3 * * * docker compose -f /path/to/zclaw/docker-compose.yml exec -T postgres pg_dump -U zclaw zclaw_saas | gzip > /path/to/backups/zclaw_\$(date +\%Y\%m\%d).sql.gz" | crontab -
```
### 6.2 恢复数据库
```bash
# 从备份恢复
gunzip -c /path/to/backups/zclaw_20260331.sql.gz | \
docker compose exec -T postgres psql -U zclaw -d zclaw_saas
```
### 6.3 环境变量备份
```bash
# 安全存储 .env 文件
cp .env /path/to/backups/env_$(date +%Y%m%d)
chmod 600 /path/to/backups/env_*
```
### 6.4 备份保留策略
| 备份类型 | 保留期限 | 频率 |
|-----------|-----------|---------|
| 每日备份 | 7 天 | 每天一次 |
| 每周备份 | 4 周 | 每周日 |
| 每月备份 | 12 个月 | 每月 1 日 |
---
## 7. 常见问题
### 7.1 服务无法启动
**症状**: `docker compose up` 后 saas 容器立即退出
**排查步骤**:
```bash
# 查看容器日志
docker compose logs saas
# 检查环境变量是否正确
docker compose config
# 检查数据库是否就绪
docker compose exec postgres pg_isready -U zclaw
```
**常见原因**:
- 数据库 URL 格式错误
- JWT 密钥未设置
- PostgreSQL 尚未就绪healthcheck 未通过)
### 7.2 数据库连接失败
**症状**: 日志显示 `connection refused``authentication failed`
**排查步骤**:
```bash
# 检查数据库容器状态
docker compose ps postgres
# 验证连接字符串
docker compose exec saas env | grep DATABASE_URL
# 测试数据库连接
docker compose exec postgres psql -U zclaw -d zclaw_saas -c "SELECT 1"
```
### 7.3 502 Bad Gateway
**症状**: Nginx 返回 502 错误
**排查步骤**:
```bash
# 检查 SaaS 后端是否运行
curl http://127.0.0.1:8080/health
# 检查 Nginx 错误日志
sudo tail -50 /var/log/nginx/zclaw-saas-error.log
# 检查端口绑定
ss -tlnp | grep 8080
```
### 7.4 SSE 流式响应中断
**症状**: 聊天响应在中间断开
**解决方案**:
- 确认 Nginx `proxy_buffering off` 已设置
- 增大 `proxy_read_timeout`(默认 300s
- 检查 Nginx 错误日志是否有 upstream timeout
### 7.5 Docker 构建缓慢
**症状**: `docker build` 耗时很长
**优化**:
- Dockerfile 已配置依赖缓存层,首次构建后仅重建源码变更
- 确保 `.dockerignore` 排除了 `target/``node_modules/` 等大目录
- 使用 `docker compose build --parallel` 并行构建
### 7.6 磁盘空间不足
```bash
# 查看 Docker 磁盘使用
docker system df
# 清理未使用的资源
docker system prune -a --volumes
# 查看数据库大小
docker compose exec postgres psql -U zclaw -c "SELECT pg_size_pretty(pg_database_size('zclaw_saas'));"
```
---
## 附录: 完整部署检查清单
```
[ ] 1. 服务器准备
[ ] 2GB+ RAM, 20GB+ 磁盘
[ ] Docker + Docker Compose 已安装
[ ] 防火墙已配置(仅 22/80/443
[ ] 2. 应用配置
[ ] .env 已从模板创建并填入真实值
[ ] 所有密钥已用 openssl rand -hex 32 生成
[ ] ZCLAW_SAAS_DEV=false
[ ] saas-config.toml cors_origins 已更新
[ ] 3. 启动服务
[ ] docker compose up -d --build 成功
[ ] docker compose ps 显示所有容器 healthy
[ ] curl http://localhost:8080/health 返回 200
[ ] 4. HTTPS 配置
[ ] SSL 证书已获取
[ ] Nginx 配置已部署并测试 (nginx -t)
[ ] HTTP 正确重定向到 HTTPS
[ ] https://api.yourdomain.com/health 返回 200
[ ] 5. 安全验证
[ ] 安全头存在curl -I https://api.yourdomain.com
[ ] 5432 端口外部不可访问
[ ] 8080 端口仅 127.0.0.1 可访问
[ ] .env 文件权限 600
[ ] 6. 备份
[ ] 数据库自动备份已配置
[ ] 备份恢复测试已通过
```

35
saas-env.example Normal file
View File

@@ -0,0 +1,35 @@
# ============================================================
# ZCLAW SaaS Backend - Environment Variables
# ============================================================
# Usage:
# cp saas-env.example .env
# # Edit .env with your actual values
# docker compose up -d
# ============================================================
# ---- PostgreSQL 配置 ----
POSTGRES_USER=zclaw
POSTGRES_PASSWORD=请修改为强密码
POSTGRES_DB=zclaw_saas
# ---- SaaS 后端配置 ----
# 数据库连接 URL (Docker 内部网络使用主机名 postgres)
ZCLAW_DATABASE_URL=postgres://zclaw:请修改为强密码@postgres:5432/zclaw_saas
# JWT 签名密钥 (至少32字符随机字符串)
# 生成命令: openssl rand -hex 32
ZCLAW_SAAS_JWT_SECRET=请使用openssl_rand_hex_32生成
# TOTP/API Key 加密密钥 (64字符hex)
# 生成命令: openssl rand -hex 32
ZCLAW_TOTP_ENCRYPTION_KEY=请使用openssl_rand_hex_32生成
# ---- 初始管理员账号 ----
ZCLAW_ADMIN_USERNAME=admin
ZCLAW_ADMIN_PASSWORD=请修改为强密码
# ---- 运行模式 ----
# 开发模式 (生产环境必须为 false 或不设置)
# 设置为 true 会放宽安全限制: Cookie Secure=false, CORS 宽松等
ZCLAW_SAAS_DEV=false