From 8adb4d5cefefa0fd95c0576d16e61e7a417d964b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Mon, 1 Jun 2026 13:53:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E9=94=AE=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DEPLOY_LINUX_DOCKER_ZH.md | 359 ++++++++++++++++++ ...QL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md | 12 +- offline-backend-20260512/deploy.sh | 263 +++++++++++++ offline-backend-20260512/docker-compose.yaml | 50 +++ .../scripts/container-entrypoint.sh | 67 ++++ 5 files changed, 749 insertions(+), 2 deletions(-) create mode 100644 offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md create mode 100644 offline-backend-20260512/deploy.sh create mode 100644 offline-backend-20260512/docker-compose.yaml create mode 100644 offline-backend-20260512/scripts/container-entrypoint.sh diff --git a/offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md b/offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md new file mode 100644 index 00000000..359eaac3 --- /dev/null +++ b/offline-backend-20260512/DEPLOY_LINUX_DOCKER_ZH.md @@ -0,0 +1,359 @@ +# DeerFlow 后端 Linux Docker 部署指南 + +本文档说明如何在 **Linux + Docker** 环境下一键部署 DeerFlow 后端(Gateway API,默认端口 **8001**)。 + +部署目录:`offline-backend-20260512/` + +--- + +## 1. 架构概览 + +```text +┌─────────────────────────────────────────────────────┐ +│ Linux 宿主机 │ +│ ┌───────────────────────────────────────────────┐ │ +│ │ Docker 容器: deerflow-backend │ │ +│ │ ┌─────────────────────────────────────────┐ │ │ +│ │ │ Gateway API (uvicorn, port 8001) │ │ │ +│ │ │ + LangGraph Agent Runtime │ │ │ +│ │ └─────────────────────────────────────────┘ │ │ +│ │ 挂载卷: │ │ +│ │ config.yaml / .env │ │ +│ │ backend/ 源码(可热更新 Python 代码) │ │ +│ │ data/ → .deer-flow 持久化数据 │ │ +│ └───────────────────────────────────────────────┘ │ +│ │ 8001 │ +│ ▼ │ +│ 前端 / Nginx / 其他服务 │ +└─────────────────────────────────────────────────────┘ +``` + +**持久化数据**(挂载到 `data/`): + +| 路径 | 说明 | +|------|------| +| `data/data/deerflow.db` 或 `checkpoints.db` | LangGraph checkpoint(SQLite) | +| `data/data/` | 业务 SQLite 文件(未切 MySQL 时) | +| `data/python-vendor/` | 离线安装的 Python 包(asyncmy、croniter 等) | +| `data/users/` | 用户线程、上传、记忆等 | +| `data/archives/` | 会话归档 | + +--- + +## 2. 服务器要求 + +| 项目 | 最低要求 | +|------|----------| +| 操作系统 | Linux x86_64(CentOS 7+、Ubuntu 20.04+、Debian 11+ 等) | +| Docker | 20.10+ | +| Docker Compose | V2(`docker compose` 命令可用) | +| 内存 | 建议 4 GB+ | +| 磁盘 | 建议 20 GB+(含镜像、数据、日志) | +| 网络 | 能访问 LLM API;内网部署可配置 Python 私服 | + +### 2.1 安装 Docker(如未安装) + +**Ubuntu / Debian:** + +```bash +curl -fsSL https://get.docker.com | sh +sudo usermod -aG docker "$USER" +newgrp docker +``` + +**验证:** + +```bash +docker --version +docker compose version +``` + +--- + +## 3. 部署包准备 + +将以下内容上传到服务器(例如 `/opt/deerflow/offline-backend-20260512`): + +```text +offline-backend-20260512/ +├── deploy.sh # 一键部署脚本(主入口) +├── start_backend.sh # 启动(deploy.sh 的别名) +├── stop_backend.sh # 停止 +├── docker-compose.yaml +├── .env.example +├── config.yaml # 应用配置 +├── skill_evolution.json +├── backend/ # 后端源码 + Dockerfile + wheelhouse +│ ├── Dockerfile +│ ├── wheelhouse/ # 离线 Python 包 +│ └── ... +├── scripts/ +│ └── container-entrypoint.sh +└── data/ # 首次部署后自动创建 +``` + +--- + +## 4. 一键部署(推荐) + +### 4.1 配置环境变量 + +```bash +cd /opt/deerflow/offline-backend-20260512 + +cp .env.example .env +vi .env +``` + +**必须修改的项:** + +```env +# 至少配置一个可用模型的 API Key +DEEPSEEK_API_KEY=sk-xxxxxxxx + +# JWT 密钥,部署后不要随意更改 +AUTH_JWT_SECRET=your-long-random-secret + +# 前端实际访问地址(CORS) +GATEWAY_CORS_ORIGINS=http://192.168.1.100:5174,http://localhost:5174 +``` + +**使用 MySQL 时**(`config.yaml` 中 `database.backend: mysql`): + +```env +# 不要用 127.0.0.1 指向宿主机 MySQL;密码特殊字符须 URL 编码 +MYSQL_DATABASE_URL=mysql+asyncmy://user:pass@192.168.1.100:3306/deerflow?charset=utf8mb4 +``` + +**内网无法访问公网 PyPI 时:** + +```env +UV_INDEX_URL=http://your-python-mirror/simple +PIP_INDEX_URL=http://your-python-mirror/simple +UV_TRUSTED_HOST=your-python-mirror +PIP_TRUSTED_HOST=your-python-mirror +``` + +### 4.2 检查 config.yaml + +确认模型配置、沙箱模式、数据库配置正确。`config.yaml` 中与 `$` 开头的值会从 `.env` 读取,例如 `$MYSQL_DATABASE_URL`。 + +### 4.3 执行一键部署 + +```bash +chmod +x deploy.sh start_backend.sh stop_backend.sh scripts/container-entrypoint.sh +bash deploy.sh +``` + +脚本会自动: + +1. 检查 Docker 环境 +2. 创建 `data/` 目录和兼容符号链接 +3. 若无 `.env` 则从 `.env.example` 复制 +4. 构建 Docker 镜像(或加载离线 tar) +5. 启动容器并做健康检查 + +### 4.4 验证 + +```bash +curl http://127.0.0.1:8001/health +# 期望返回 JSON,HTTP 200 + +curl -s http://127.0.0.1:8001/openapi.json | head +``` + +浏览器访问 API 文档:`http://<服务器IP>:8001/docs` + +--- + +## 5. 常用运维命令 + +| 操作 | 命令 | +|------|------| +| 一键部署/启动 | `bash deploy.sh` 或 `bash start_backend.sh` | +| 停止 | `bash deploy.sh down` 或 `bash stop_backend.sh` | +| 重启 | `bash deploy.sh restart` | +| 查看状态 | `bash deploy.sh status` | +| 查看日志 | `bash deploy.sh logs` | +| 仅构建镜像 | `bash deploy.sh build` | +| 健康检查 | `bash deploy.sh health` | + +### 5.1 自定义端口 + +```bash +HOST_PORT=9001 bash deploy.sh up +``` + +### 5.2 离线镜像模式(无法构建时) + +若已提供预构建镜像 tar: + +```bash +IMAGE_TAR=./deerflow-backend-offline-image-20260505.tar \ +IMAGE_TAG=deerflow-backend-offline:20260505 \ +DEPLOY_MODE=offline \ +bash deploy.sh up +``` + +或分步: + +```bash +bash deploy.sh load +IMAGE_TAG=deerflow-backend-offline:20260505 bash deploy.sh up +``` + +--- + +## 6. 前端对接 + +前端需配置后端地址(Vite 环境变量示例): + +```env +VITE_BACKEND_BASE_URL=http://192.168.1.100:8001 +VITE_LANGGRAPH_BASE_URL=http://192.168.1.100:8001/api +``` + +确保 `.env` 中 `GATEWAY_CORS_ORIGINS` 包含前端实际 Origin。 + +--- + +## 7. MySQL 切换(可选) + +若初始使用 SQLite,后续可切换到 MySQL: + +```bash +bash stop_backend.sh + +MYSQL_DATABASE_URL='mysql+asyncmy://user:pass@192.168.1.100:3306/deerflow?charset=utf8mb4' \ +bash switch_database_to_mysql.sh + +bash start_backend.sh +``` + +详细说明见 [MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md](./MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md)。 + +--- + +## 8. 代码热更新 + +当前部署方式将 `backend/app`、`backend/packages` 等目录挂载进容器,**更新 Python 代码后重启即可生效**,无需重新构建镜像: + +```bash +# 上传新代码后 +bash deploy.sh restart +``` + +若修改了 `pyproject.toml` / 依赖,需重新构建: + +```bash +bash deploy.sh build +bash deploy.sh restart +``` + +--- + +## 9. 目录与卷说明 + +| 宿主机 | 容器内 | 说明 | +|--------|--------|------| +| `./config.yaml` | `/app/config.yaml` | 主配置(只读) | +| `./.env` | 环境变量注入 | 密钥、CORS 等 | +| `./backend/app` | `/app/backend/app` | 应用层代码 | +| `./backend/packages` | `/app/backend/packages` | harness 框架 | +| `./backend/skills` | `/app/backend/skills` | Agent Skills | +| `./backend/wheelhouse` | `/app/wheelhouse` | 离线 wheel 包 | +| `./data` | `/app/backend/.deer-flow` | 全部运行时数据 | + +--- + +## 10. 故障排查 + +### 10.1 容器启动后立即退出 + +```bash +bash deploy.sh logs +``` + +常见原因: + +- `.env` 中 API Key 或 `AUTH_JWT_SECRET` 未配置 +- `config.yaml` 语法错误 +- MySQL 连接失败(检查 `MYSQL_DATABASE_URL` 是否用了容器可达的 IP) +- 缺少 `asyncmy`(MySQL 模式):确认 `backend/wheelhouse` 中有对应 wheel,或配置 Python 私服 + +### 10.2 健康检查失败 + +```bash +docker ps -a +docker logs deerflow-backend --tail 100 +curl -v http://127.0.0.1:8001/health +``` + +确认端口未被占用: + +```bash +ss -tlnp | grep 8001 +``` + +### 10.3 前端报 CORS / Failed to fetch + +检查 `.env`: + +```env +GATEWAY_CORS_ORIGINS=http://你的前端地址:端口 +``` + +修改后重启: + +```bash +bash deploy.sh restart +``` + +### 10.4 Docker 权限问题 + +```bash +sudo usermod -aG docker "$USER" +newgrp docker +``` + +### 10.5 沙箱需要 Docker-in-Docker + +若 `config.yaml` 使用 `AioSandboxProvider`,容器需挂载宿主机 Docker socket(默认已挂载 `/var/run/docker.sock`)。确认: + +```bash +ls -l /var/run/docker.sock +``` + +--- + +## 11. 生产环境建议 + +1. **反向代理**:前面加 Nginx,配置 HTTPS 和限流 +2. **防火墙**:仅开放必要端口(如 443、8001 内网) +3. **备份**:定期备份 `data/` 目录和 `config.yaml`、`.env` +4. **日志**:配置 Docker 日志轮转(`/etc/docker/daemon.json` 中 `log-opts`) +5. **资源**:根据并发调整 `GATEWAY_WORKERS`(`.env`,默认 2) +6. **文档**:生产环境建议 `GATEWAY_ENABLE_DOCS=false` + +--- + +## 12. 回滚 + +```bash +bash stop_backend.sh + +# 恢复配置和数据备份 +cp data/backups/xxx/config.yaml.bak config.yaml +cp data/backups/xxx/env.bak .env +cp -a data.bak data + +bash start_backend.sh +``` + +--- + +## 13. 相关文档 + +- [MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md](./MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md) — MySQL 切换 +- [backend/docs/BACKEND_RUN_STOP_ZH.md](./backend/docs/BACKEND_RUN_STOP_ZH.md) — 本地非 Docker 启停 +- [backend/docs/CONFIGURATION.md](./backend/docs/CONFIGURATION.md) — 配置项详解 diff --git a/offline-backend-20260512/MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md b/offline-backend-20260512/MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md index 2fc884b7..4f3f6551 100644 --- a/offline-backend-20260512/MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md +++ b/offline-backend-20260512/MYSQL_SWITCH_RELEASE_NOTES_LINUX_DOCKER_ZH.md @@ -1,13 +1,21 @@ # Linux Docker 版:业务库切换 MySQL 发版说明 -适用场景:Linux 服务器上使用 `offline-backend-20260512/start_backend.sh` 启动 Docker 后端。 +适用场景:Linux 服务器上使用 Docker 部署 DeerFlow 后端。 -原启动脚本保持不变: +推荐一键部署: + +```bash +bash deploy.sh +``` + +或使用兼容脚本: ```bash bash start_backend.sh ``` +详细步骤见 [DEPLOY_LINUX_DOCKER_ZH.md](./DEPLOY_LINUX_DOCKER_ZH.md)。 + 单独新增切换脚本: ```bash diff --git a/offline-backend-20260512/deploy.sh b/offline-backend-20260512/deploy.sh new file mode 100644 index 00000000..49b2d470 --- /dev/null +++ b/offline-backend-20260512/deploy.sh @@ -0,0 +1,263 @@ +#!/usr/bin/env bash +# +# deploy.sh — DeerFlow 后端 Linux Docker 一键部署脚本 +# +# 用法: +# bash deploy.sh # 首次部署: 检查环境 → 构建/加载镜像 → 启动 +# bash deploy.sh up # 启动服务 +# bash deploy.sh down # 停止并移除容器 +# bash deploy.sh restart # 重启 +# bash deploy.sh status # 查看状态 +# bash deploy.sh logs # 跟踪日志 +# bash deploy.sh build # 仅构建镜像 +# bash deploy.sh load # 从离线 tar 加载镜像 +# bash deploy.sh health # 健康检查 +# +# 环境变量(可选): +# HOST_PORT 宿主机映射端口,默认 8001 +# CONTAINER_NAME 容器名,默认 deerflow-backend +# IMAGE_TAG 镜像标签,默认 deerflow-backend:latest +# IMAGE_TAR 离线镜像 tar 路径 +# DEPLOY_MODE build | offline,默认 build +# SKIP_BUILD 1 时跳过构建/加载,直接启动 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +COMPOSE_PROJECT="${COMPOSE_PROJECT:-deerflow-backend}" +COMPOSE_FILE="${SCRIPT_DIR}/docker-compose.yaml" +COMPOSE=(docker compose -p "${COMPOSE_PROJECT}" -f "${COMPOSE_FILE}") + +HOST_PORT="${HOST_PORT:-8001}" +CONTAINER_NAME="${CONTAINER_NAME:-deerflow-backend}" +IMAGE_TAG="${IMAGE_TAG:-deerflow-backend:latest}" +IMAGE_TAR="${IMAGE_TAR:-${SCRIPT_DIR}/deerflow-backend-offline-image.tar}" +DEPLOY_MODE="${DEPLOY_MODE:-build}" +SKIP_BUILD="${SKIP_BUILD:-0}" + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' + +info() { echo -e "${BLUE}[INFO]${NC} $*"; } +ok() { echo -e "${GREEN}[ OK ]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +fail() { echo -e "${RED}[FAIL]${NC} $*" >&2; exit 1; } + +usage() { + sed -n '3,18p' "$0" | sed 's/^# \?//' + exit 0 +} + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || fail "未找到命令: $1" +} + +ensure_layout() { + mkdir -p "${SCRIPT_DIR}/data/data" "${SCRIPT_DIR}/data/backups" "${SCRIPT_DIR}/data/archives/conversations" + + # switch_database_to_mysql.sh 等脚本期望根目录有这些路径 + [ -e "${SCRIPT_DIR}/wheelhouse" ] || ln -sfn backend/wheelhouse "${SCRIPT_DIR}/wheelhouse" + [ -e "${SCRIPT_DIR}/skills" ] || ln -sfn backend/skills "${SCRIPT_DIR}/skills" + if [ ! -e "${SCRIPT_DIR}/extensions_config.json" ]; then + ln -sfn backend/extensions_config.json "${SCRIPT_DIR}/extensions_config.json" + fi + + chmod +x "${SCRIPT_DIR}/scripts/container-entrypoint.sh" 2>/dev/null || true +} + +ensure_env() { + if [ ! -f "${SCRIPT_DIR}/.env" ]; then + if [ -f "${SCRIPT_DIR}/.env.example" ]; then + cp "${SCRIPT_DIR}/.env.example" "${SCRIPT_DIR}/.env" + warn "已从 .env.example 生成 .env,部署前请编辑 API Key 和 AUTH_JWT_SECRET" + else + fail "缺少 .env 文件,请先创建并配置环境变量" + fi + fi + + if [ ! -f "${SCRIPT_DIR}/config.yaml" ]; then + fail "缺少 config.yaml,请复制并编辑 config.example.yaml" + fi + + if [ ! -f "${SCRIPT_DIR}/skill_evolution.json" ]; then + echo '{"enabled":false,"curator":{"enabled":false}}' > "${SCRIPT_DIR}/skill_evolution.json" + warn "已创建默认 skill_evolution.json" + fi +} + +check_docker() { + require_cmd docker + docker info >/dev/null 2>&1 || fail "Docker 未运行或无权限,请确认 docker 服务已启动且当前用户在 docker 组" + docker compose version >/dev/null 2>&1 || fail "未找到 docker compose,请安装 Docker Compose V2" +} + +generate_secret_if_needed() { + local key="$1" + local file="$2" + if grep -qE "^[[:space:]]*${key}=" "${SCRIPT_DIR}/.env" 2>/dev/null; then + local val + val="$(grep -E "^[[:space:]]*${key}=" "${SCRIPT_DIR}/.env" | tail -n1 | sed 's/^[^=]*=//')" + if [ -n "${val}" ] && [ "${val}" != "change-me-to-a-long-random-string" ]; then + return 0 + fi + fi + if [ -f "${file}" ]; then + return 0 + fi + mkdir -p "$(dirname "${file}")" + if command -v openssl >/dev/null 2>&1; then + openssl rand -hex 32 > "${file}" + chmod 600 "${file}" + warn "已生成 ${key} → ${file},建议将其写入 .env 后删除该文件" + fi +} + +load_offline_image() { + if docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then + ok "镜像已存在: ${IMAGE_TAG}" + return 0 + fi + [ -f "${IMAGE_TAR}" ] || fail "离线镜像不存在: ${IMAGE_TAR},请设置 IMAGE_TAR 或先执行 bash deploy.sh build" + info "加载离线镜像: ${IMAGE_TAR}" + docker load -i "${IMAGE_TAR}" + ok "镜像加载完成: ${IMAGE_TAG}" +} + +build_image() { + info "构建 Docker 镜像: ${IMAGE_TAG}" + export IMAGE_TAG + "${COMPOSE[@]}" build gateway + ok "镜像构建完成" +} + +prepare_image() { + if [ "${SKIP_BUILD}" = "1" ]; then + info "SKIP_BUILD=1,跳过构建/加载" + return 0 + fi + if docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then + ok "使用已有镜像: ${IMAGE_TAG}" + return 0 + fi + case "${DEPLOY_MODE}" in + offline) + load_offline_image + ;; + build|*) + build_image + ;; + esac +} + +wait_for_health() { + local url="http://127.0.0.1:${HOST_PORT}/health" + local retries="${HEALTH_RETRIES:-30}" + local i=1 + info "等待服务就绪: ${url}" + while [ "${i}" -le "${retries}" ]; do + if curl -fsS "${url}" >/dev/null 2>&1; then + ok "健康检查通过" + return 0 + fi + sleep 2 + i=$((i + 1)) + done + warn "健康检查超时,请执行: bash deploy.sh logs" + return 1 +} + +cmd_up() { + check_docker + ensure_layout + ensure_env + generate_secret_if_needed "AUTH_JWT_SECRET" "${SCRIPT_DIR}/data/.auth-jwt-secret" + + export HOST_PORT CONTAINER_NAME IMAGE_TAG + + if ! docker ps -a --format '{{.Names}}' | grep -qx "${CONTAINER_NAME}"; then + prepare_image + fi + + info "启动容器..." + "${COMPOSE[@]}" up -d --remove-orphans + ok "容器已启动: ${CONTAINER_NAME}" + echo + echo " API 地址: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 127.0.0.1):${HOST_PORT}" + echo " 健康检查: http://127.0.0.1:${HOST_PORT}/health" + echo " API 文档: http://127.0.0.1:${HOST_PORT}/docs" + echo + wait_for_health || true +} + +cmd_down() { + check_docker + export HOST_PORT CONTAINER_NAME IMAGE_TAG + info "停止容器..." + "${COMPOSE[@]}" down --remove-orphans + ok "已停止" +} + +cmd_restart() { + cmd_down + cmd_up +} + +cmd_status() { + check_docker + export HOST_PORT CONTAINER_NAME IMAGE_TAG + "${COMPOSE[@]}" ps + echo + if docker ps --format '{{.Names}}' | grep -qx "${CONTAINER_NAME}"; then + curl -fsS "http://127.0.0.1:${HOST_PORT}/health" 2>/dev/null && echo || warn "健康检查失败" + else + warn "容器未运行" + fi +} + +cmd_logs() { + check_docker + export HOST_PORT CONTAINER_NAME IMAGE_TAG + "${COMPOSE[@]}" logs -f --tail=200 gateway +} + +cmd_build() { + check_docker + ensure_layout + build_image +} + +cmd_load() { + check_docker + DEPLOY_MODE=offline + load_offline_image +} + +cmd_health() { + curl -fsS "http://127.0.0.1:${HOST_PORT}/health" && echo || fail "健康检查失败" +} + +main() { + local cmd="${1:-up}" + case "${cmd}" in + -h|--help|help) usage ;; + up|start|"") cmd_up ;; + down|stop) cmd_down ;; + restart) cmd_restart ;; + status) cmd_status ;; + logs) cmd_logs ;; + build) cmd_build ;; + load) cmd_load ;; + health) cmd_health ;; + deploy) cmd_up ;; + *) + fail "未知命令: ${cmd}。运行 bash deploy.sh --help 查看帮助" + ;; + esac +} + +main "${1:-up}" diff --git a/offline-backend-20260512/docker-compose.yaml b/offline-backend-20260512/docker-compose.yaml new file mode 100644 index 00000000..0a67f1b2 --- /dev/null +++ b/offline-backend-20260512/docker-compose.yaml @@ -0,0 +1,50 @@ +# DeerFlow 后端 — Linux Docker 部署 +# 推荐使用 deploy.sh 管理;也可直接: docker compose up -d + +services: + gateway: + build: + context: . + dockerfile: backend/Dockerfile + args: + APT_MIRROR: ${APT_MIRROR:-} + UV_IMAGE: ${UV_IMAGE:-ghcr.io/astral-sh/uv:0.7.20} + UV_INDEX_URL: ${UV_INDEX_URL:-https://pypi.org/simple} + UV_EXTRAS: ${UV_EXTRAS:-} + image: ${IMAGE_TAG:-deerflow-backend:latest} + container_name: ${CONTAINER_NAME:-deerflow-backend} + ports: + - "${HOST_PORT:-8001}:8001" + env_file: + - .env + environment: + CI: "true" + DEER_FLOW_PROJECT_ROOT: /app + DEER_FLOW_HOME: /app/backend/.deer-flow + DEER_FLOW_CONFIG_PATH: /app/config.yaml + DEER_FLOW_EXTENSIONS_CONFIG_PATH: /app/backend/extensions_config.json + DEER_FLOW_SKILL_EVOLUTION_CONFIG_PATH: /app/skill_evolution.json + DEER_FLOW_SKILLS_PATH: /app/backend/skills + DEER_FLOW_CHANNELS_LANGGRAPH_URL: ${DEER_FLOW_CHANNELS_LANGGRAPH_URL:-http://gateway:8001/api} + DEER_FLOW_CHANNELS_GATEWAY_URL: ${DEER_FLOW_CHANNELS_GATEWAY_URL:-http://gateway:8001} + UV_LINK_MODE: copy + volumes: + - ./config.yaml:/app/config.yaml:ro + - ./skill_evolution.json:/app/skill_evolution.json:ro + - ./backend/app:/app/backend/app + - ./backend/packages:/app/backend/packages + - ./backend/scripts:/app/backend/scripts + - ./backend/extensions_config.json:/app/backend/extensions_config.json:ro + - ./backend/skills:/app/backend/skills:ro + - ./backend/wheelhouse:/app/wheelhouse:ro + - ./backend/pyproject.toml:/app/backend/pyproject.toml:ro + - ./backend/uv.lock:/app/backend/uv.lock:ro + - ./backend/langgraph.json:/app/backend/langgraph.json:ro + - ./scripts/container-entrypoint.sh:/app/scripts/container-entrypoint.sh:ro + - ./data:/app/backend/.deer-flow + - ${DEER_FLOW_DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock + working_dir: /app + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped + command: ["sh", "/app/scripts/container-entrypoint.sh"] diff --git a/offline-backend-20260512/scripts/container-entrypoint.sh b/offline-backend-20260512/scripts/container-entrypoint.sh new file mode 100644 index 00000000..f6eda632 --- /dev/null +++ b/offline-backend-20260512/scripts/container-entrypoint.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# Container startup: install optional offline deps, then launch Gateway. +set -eu + +cd /app/backend + +VENDOR_DIR="/app/backend/.deer-flow/python-vendor" +WHEELHOUSE="/app/wheelhouse" +mkdir -p "${VENDOR_DIR}" + +export PYTHONPATH=".:${VENDOR_DIR}" + +pip_install_offline() { + local packages="$1" + if [ -d "${WHEELHOUSE}" ]; then + uv pip install \ + --target "${VENDOR_DIR}" \ + --no-index \ + --find-links "${WHEELHOUSE}" \ + ${packages} + elif [ -n "${UV_INDEX_URL:-}" ] || [ -n "${PIP_INDEX_URL:-}" ]; then + uv pip install --target "${VENDOR_DIR}" ${packages} + else + uv pip install --target "${VENDOR_DIR}" ${packages} + fi +} + +pip_install_online() { + local packages="$1" + if [ -n "${UV_INDEX_URL:-}" ]; then + uv pip install --target "${VENDOR_DIR}" ${packages} + elif [ -n "${PIP_INDEX_URL:-}" ]; then + uv pip install --target "${VENDOR_DIR}" --index-url "${PIP_INDEX_URL}" ${packages} + else + uv pip install --target "${VENDOR_DIR}" ${packages} + fi +} + +install_if_missing() { + local module="$1" + local packages="$2" + if uv run --no-sync python -c "import ${module}" >/dev/null 2>&1; then + return 0 + fi + echo "Installing ${packages} into ${VENDOR_DIR}" + if [ -d "${WHEELHOUSE}" ] && ls "${WHEELHOUSE}"/*.whl >/dev/null 2>&1; then + pip_install_offline "${packages}" + else + pip_install_online "${packages}" + fi +} + +if [ -n "${MYSQL_DATABASE_URL:-}" ]; then + install_if_missing "asyncmy" "${MYSQL_PIP_PACKAGES:-asyncmy==0.2.10}" +fi + +if [ "${SCHEDULER_INSTALL_DEPS:-1}" = "1" ]; then + install_if_missing "croniter" "${SCHEDULER_PIP_PACKAGES:-croniter==6.2.2}" +fi + +WORKERS="${GATEWAY_WORKERS:-2}" +echo "Starting Gateway on 0.0.0.0:8001 (workers=${WORKERS})" + +exec env PYTHONPATH=".:${VENDOR_DIR}" uv run --no-sync uvicorn app.gateway.app:app \ + --host 0.0.0.0 \ + --port 8001 \ + --workers "${WORKERS}"