From 55b5c2cc9fd604204a5ce6867923450b47060495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Sat, 11 Jul 2026 18:58:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=83=A8=E7=BD=B2=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/.dockerignore | 17 + .../backend/DOCKER_OFFLINE.md | 52 +++ .../backend/Dockerfile.backend-offline | 108 +++++ .../backend/build-backend-image.sh | 32 ++ .../backend/save-backend-image.sh | 13 + .../backend/start-backend-docker.sh | 102 +++++ .../backend/后台离线部署说明.md | 403 ++++++++++++++++++ 7 files changed, 727 insertions(+) create mode 100644 offline-backend-20260512/backend/.dockerignore create mode 100644 offline-backend-20260512/backend/DOCKER_OFFLINE.md create mode 100644 offline-backend-20260512/backend/Dockerfile.backend-offline create mode 100644 offline-backend-20260512/backend/build-backend-image.sh create mode 100644 offline-backend-20260512/backend/save-backend-image.sh create mode 100644 offline-backend-20260512/backend/start-backend-docker.sh create mode 100644 offline-backend-20260512/backend/后台离线部署说明.md diff --git a/offline-backend-20260512/backend/.dockerignore b/offline-backend-20260512/backend/.dockerignore new file mode 100644 index 00000000..eafc1914 --- /dev/null +++ b/offline-backend-20260512/backend/.dockerignore @@ -0,0 +1,17 @@ +.git +.pytest_cache +.ruff_cache +.venv +__pycache__ +*.py[cod] +*.pyo +*.zip + +# Runtime state is mounted from the host in Docker deployments. +.deer-flow +logs + +# Local/offline artifacts should not be baked into the runtime image. +docker-data +dist +wheelhouse diff --git a/offline-backend-20260512/backend/DOCKER_OFFLINE.md b/offline-backend-20260512/backend/DOCKER_OFFLINE.md new file mode 100644 index 00000000..d4e9d7d9 --- /dev/null +++ b/offline-backend-20260512/backend/DOCKER_OFFLINE.md @@ -0,0 +1,52 @@ +# Backend Docker Offline Deployment + +Build once on an online Linux server: + +```bash +cd offline-backend-20260512/backend +chmod +x build-backend-image.sh save-backend-image.sh start-backend-docker.sh +./build-backend-image.sh +./save-backend-image.sh +``` + +The build script defaults to Aliyun for apt packages and Tsinghua for Python packages: + +```bash +APT_MIRROR=mirrors.aliyun.com \ +UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple/ \ +./build-backend-image.sh +``` + +The Docker build installs the pinned `requirements-offline.txt` with pip from +the configured Python mirror, so the dependency image contains the backend +runtime dependencies and can be moved to an offline machine. + +Move `dist/deerflow-backend-offline.tar` plus this backend directory to the +offline machine, then load and start: + +```bash +docker load -i deerflow-backend-offline.tar +./start-backend-docker.sh 8001 +``` + +Useful overrides: + +```bash +BACKEND_PORT=9001 ./start-backend-docker.sh +IMAGE_NAME=your-image:tag ./start-backend-docker.sh 9001 +DATA_DIR=/data/deerflow-backend ./start-backend-docker.sh +``` + +The startup script bind-mounts these host paths: + +- backend code: current backend directory -> `/app/backend` +- config: `docker-data/config/config.yaml`, `extensions_config.json`, `.env` +- agents and SQLite/runtime data: `docker-data/.deer-flow` +- skills: `docker-data/skills` +- logs: `docker-data/logs` + +Edit files under `docker-data/` on the host, then restart the container: + +```bash +docker restart deerflow-backend +``` diff --git a/offline-backend-20260512/backend/Dockerfile.backend-offline b/offline-backend-20260512/backend/Dockerfile.backend-offline new file mode 100644 index 00000000..bf562cf8 --- /dev/null +++ b/offline-backend-20260512/backend/Dockerfile.backend-offline @@ -0,0 +1,108 @@ +# Backend-only DeerFlow image for offline delivery. +# +# Build online once, then export with: +# docker save deerflow-backend-offline:latest -o deerflow-backend-offline.tar +# +# Runtime code/config/data are bind-mounted by start-backend-docker.sh so the +# customer can edit code, agents, skills, config.yaml, .env, and SQLite data +# without rebuilding this dependency image. + +ARG PYTHON_IMAGE=python:3.12-slim-bookworm + +FROM ${PYTHON_IMAGE} AS builder + +ARG APT_MIRROR=mirrors.aliyun.com +ARG UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple/ +ARG UV_VERSION=0.7.20 +ARG UV_NO_CACHE=1 +ARG UV_COMPILE_BYTECODE=0 + +ENV UV_PROJECT_ENVIRONMENT=/opt/venv \ + UV_INDEX_URL=${UV_INDEX_URL} \ + UV_LINK_MODE=copy \ + UV_NO_CACHE=${UV_NO_CACHE} \ + UV_COMPILE_BYTECODE=${UV_COMPILE_BYTECODE} \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /build/backend + +RUN if [ -n "${APT_MIRROR}" ]; then \ + sed -i "s|deb.debian.org|${APT_MIRROR}|g" /etc/apt/sources.list.d/debian.sources 2>/dev/null || true; \ + sed -i "s|security.debian.org|${APT_MIRROR}|g" /etc/apt/sources.list.d/debian.sources 2>/dev/null || true; \ + sed -i "s|deb.debian.org|${APT_MIRROR}|g" /etc/apt/sources.list 2>/dev/null || true; \ + sed -i "s|security.debian.org|${APT_MIRROR}|g" /etc/apt/sources.list 2>/dev/null || true; \ + fi \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + curl \ + git \ + && rm -rf /var/lib/apt/lists/* \ + && python -m pip install --no-cache-dir -i "${UV_INDEX_URL}" "uv==${UV_VERSION}" + +ARG UV_HTTP_TIMEOUT=300 +ARG UV_CONCURRENT_DOWNLOADS=4 +ARG UV_CONCURRENT_INSTALLS=4 +ARG UV_CONCURRENT_BUILDS=2 + +ENV UV_HTTP_TIMEOUT=${UV_HTTP_TIMEOUT} \ + UV_CONCURRENT_DOWNLOADS=${UV_CONCURRENT_DOWNLOADS} \ + UV_CONCURRENT_INSTALLS=${UV_CONCURRENT_INSTALLS} \ + UV_CONCURRENT_BUILDS=${UV_CONCURRENT_BUILDS} + +COPY pyproject.toml uv.lock README.md requirements-offline.txt ./ +COPY packages ./packages + +RUN python -m venv /opt/venv \ + && /opt/venv/bin/python -m pip install --no-cache-dir --upgrade -i "${UV_INDEX_URL}" pip setuptools wheel \ + && sed -E '/--hash=/d; s/[[:space:]]+\\$//; s|^-e[[:space:]]+./packages/harness$|./packages/harness|' requirements-offline.txt > /tmp/requirements-docker.txt \ + && /opt/venv/bin/pip install --no-cache-dir -i "${UV_INDEX_URL}" -r /tmp/requirements-docker.txt \ + && /opt/venv/bin/pip install --no-cache-dir -i "${UV_INDEX_URL}" "asyncmy>=0.2.11" + +COPY . . + +FROM ${PYTHON_IMAGE} AS runtime + +ARG APT_MIRROR=mirrors.aliyun.com + +ENV PATH=/opt/venv/bin:$PATH \ + PYTHONPATH=/app/backend:/app/backend/packages/harness \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + DEER_FLOW_PROJECT_ROOT=/app/backend \ + DEER_FLOW_HOME=/app/backend/.deer-flow \ + DEER_FLOW_CONFIG_PATH=/app/backend/config.yaml \ + DEER_FLOW_EXTENSIONS_CONFIG_PATH=/app/backend/extensions_config.json \ + DEER_FLOW_SKILLS_PATH=/app/backend/skills \ + BACKEND_PORT=8001 + +WORKDIR /app/backend + +RUN if [ -n "${APT_MIRROR}" ]; then \ + sed -i "s|deb.debian.org|${APT_MIRROR}|g" /etc/apt/sources.list.d/debian.sources 2>/dev/null || true; \ + sed -i "s|security.debian.org|${APT_MIRROR}|g" /etc/apt/sources.list.d/debian.sources 2>/dev/null || true; \ + sed -i "s|deb.debian.org|${APT_MIRROR}|g" /etc/apt/sources.list 2>/dev/null || true; \ + sed -i "s|security.debian.org|${APT_MIRROR}|g" /etc/apt/sources.list 2>/dev/null || true; \ + fi \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + docker.io \ + libmagic1 \ + nodejs \ + npm \ + poppler-utils \ + tini \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /opt/venv /opt/venv +COPY --from=builder /build/backend /app/backend + +EXPOSE 8001 + +ENTRYPOINT ["tini", "--"] +CMD ["sh", "-c", "cd /app/backend && exec uvicorn app.gateway.app:app --host 0.0.0.0 --port ${BACKEND_PORT:-8001}"] diff --git a/offline-backend-20260512/backend/build-backend-image.sh b/offline-backend-20260512/backend/build-backend-image.sh new file mode 100644 index 00000000..b50bc233 --- /dev/null +++ b/offline-backend-20260512/backend/build-backend-image.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +IMAGE_NAME="${IMAGE_NAME:-deerflow-backend-offline:latest}" +APT_MIRROR="${APT_MIRROR:-mirrors.aliyun.com}" +UV_INDEX_URL="${UV_INDEX_URL:-https://pypi.tuna.tsinghua.edu.cn/simple/}" +UV_HTTP_TIMEOUT="${UV_HTTP_TIMEOUT:-300}" +UV_NO_CACHE="${UV_NO_CACHE:-1}" +UV_COMPILE_BYTECODE="${UV_COMPILE_BYTECODE:-0}" +UV_CONCURRENT_DOWNLOADS="${UV_CONCURRENT_DOWNLOADS:-4}" +UV_CONCURRENT_INSTALLS="${UV_CONCURRENT_INSTALLS:-4}" +UV_CONCURRENT_BUILDS="${UV_CONCURRENT_BUILDS:-2}" +PYTHON_IMAGE="${PYTHON_IMAGE:-python:3.12-slim-bookworm}" + +cd "${SCRIPT_DIR}" + +docker build \ + -f Dockerfile.backend-offline \ + -t "${IMAGE_NAME}" \ + --build-arg "PYTHON_IMAGE=${PYTHON_IMAGE}" \ + --build-arg "APT_MIRROR=${APT_MIRROR}" \ + --build-arg "UV_INDEX_URL=${UV_INDEX_URL}" \ + --build-arg "UV_HTTP_TIMEOUT=${UV_HTTP_TIMEOUT}" \ + --build-arg "UV_NO_CACHE=${UV_NO_CACHE}" \ + --build-arg "UV_COMPILE_BYTECODE=${UV_COMPILE_BYTECODE}" \ + --build-arg "UV_CONCURRENT_DOWNLOADS=${UV_CONCURRENT_DOWNLOADS}" \ + --build-arg "UV_CONCURRENT_INSTALLS=${UV_CONCURRENT_INSTALLS}" \ + --build-arg "UV_CONCURRENT_BUILDS=${UV_CONCURRENT_BUILDS}" \ + . + +echo "Built image: ${IMAGE_NAME}" diff --git a/offline-backend-20260512/backend/save-backend-image.sh b/offline-backend-20260512/backend/save-backend-image.sh new file mode 100644 index 00000000..fedd423c --- /dev/null +++ b/offline-backend-20260512/backend/save-backend-image.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +IMAGE_NAME="${IMAGE_NAME:-deerflow-backend-offline:latest}" +OUT_DIR="${OUT_DIR:-${SCRIPT_DIR}/dist}" +OUT_FILE="${OUT_FILE:-${OUT_DIR}/deerflow-backend-offline.tar}" + +mkdir -p "${OUT_DIR}" +docker save "${IMAGE_NAME}" -o "${OUT_FILE}" + +echo "Saved image: ${OUT_FILE}" +echo "Offline load command: docker load -i ${OUT_FILE}" diff --git a/offline-backend-20260512/backend/start-backend-docker.sh b/offline-backend-20260512/backend/start-backend-docker.sh new file mode 100644 index 00000000..19cf1797 --- /dev/null +++ b/offline-backend-20260512/backend/start-backend-docker.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +IMAGE_NAME="${IMAGE_NAME:-deerflow-backend-offline:latest}" +CONTAINER_NAME="${CONTAINER_NAME:-deerflow-backend}" +BACKEND_PORT="${1:-${BACKEND_PORT:-8001}}" + +CODE_DIR="${CODE_DIR:-${SCRIPT_DIR}}" +DATA_DIR="${DATA_DIR:-${SCRIPT_DIR}/docker-data}" +CONFIG_DIR="${CONFIG_DIR:-${DATA_DIR}/config}" +RUNTIME_DIR="${RUNTIME_DIR:-${DATA_DIR}/.deer-flow}" +SKILLS_DIR="${SKILLS_DIR:-${DATA_DIR}/skills}" +KNOWLEDGE_DIR="${KNOWLEDGE_DIR:-${DATA_DIR}/knowledge}" +LOGS_DIR="${LOGS_DIR:-${DATA_DIR}/logs}" + +CONFIG_FILE="${CONFIG_DIR}/config.yaml" +EXTENSIONS_FILE="${CONFIG_DIR}/extensions_config.json" +LANGGRAPH_FILE="${CONFIG_DIR}/langgraph.json" +ENV_FILE="${CONFIG_DIR}/.env" + +copy_if_missing() { + local src="$1" + local dst="$2" + if [[ ! -e "${dst}" && -e "${src}" ]]; then + mkdir -p "$(dirname "${dst}")" + cp -a "${src}" "${dst}" + fi +} + +copy_dir_if_missing() { + local src="$1" + local dst="$2" + if [[ ! -e "${dst}" && -d "${src}" ]]; then + mkdir -p "$(dirname "${dst}")" + cp -a "${src}" "${dst}" + fi +} + +mkdir -p "${CONFIG_DIR}" "${RUNTIME_DIR}/data" "${LOGS_DIR}" + +copy_if_missing "${CODE_DIR}/config.yaml" "${CONFIG_FILE}" +copy_if_missing "${CODE_DIR}/extensions_config.json" "${EXTENSIONS_FILE}" +copy_if_missing "${CODE_DIR}/langgraph.json" "${LANGGRAPH_FILE}" +copy_if_missing "${CODE_DIR}/.env" "${ENV_FILE}" +touch "${ENV_FILE}" + +copy_dir_if_missing "${CODE_DIR}/skills" "${SKILLS_DIR}" +mkdir -p "${SKILLS_DIR}" + +copy_dir_if_missing "${CODE_DIR}/knowledge" "${KNOWLEDGE_DIR}" +mkdir -p "${KNOWLEDGE_DIR}" + +copy_dir_if_missing "${CODE_DIR}/.deer-flow/agents" "${RUNTIME_DIR}/agents" +copy_dir_if_missing "${CODE_DIR}/.deer-flow/channels" "${RUNTIME_DIR}/channels" +copy_dir_if_missing "${CODE_DIR}/.deer-flow/users" "${RUNTIME_DIR}/users" +copy_if_missing "${CODE_DIR}/.deer-flow/SOUL.md" "${RUNTIME_DIR}/SOUL.md" +copy_if_missing "${CODE_DIR}/.deer-flow/USER.md" "${RUNTIME_DIR}/USER.md" +copy_if_missing "${CODE_DIR}/.deer-flow/system_settings.json" "${RUNTIME_DIR}/system_settings.json" + +if docker ps -a --format '{{.Names}}' | grep -Fxq "${CONTAINER_NAME}"; then + docker rm -f "${CONTAINER_NAME}" >/dev/null +fi + +DOCKER_ARGS=( + run -d + --name "${CONTAINER_NAME}" + --restart unless-stopped + -p "${BACKEND_PORT}:${BACKEND_PORT}" + --add-host host.docker.internal:host-gateway + -e "BACKEND_PORT=${BACKEND_PORT}" + -e "DEER_FLOW_GATEWAY_PORT=${BACKEND_PORT}" + -e "DEER_FLOW_PROJECT_ROOT=/app/backend" + -e "DEER_FLOW_HOME=/app/backend/.deer-flow" + -e "DEER_FLOW_HOST_BASE_DIR=${RUNTIME_DIR}" + -e "DEER_FLOW_CONFIG_PATH=/app/backend/config.yaml" + -e "DEER_FLOW_EXTENSIONS_CONFIG_PATH=/app/backend/extensions_config.json" + -e "DEER_FLOW_SKILLS_PATH=/app/backend/skills" + -v "${CODE_DIR}:/app/backend" + -v "${CONFIG_FILE}:/app/backend/config.yaml" + -v "${EXTENSIONS_FILE}:/app/backend/extensions_config.json" + -v "${LANGGRAPH_FILE}:/app/backend/langgraph.json" + -v "${ENV_FILE}:/app/backend/.env" + -v "${RUNTIME_DIR}:/app/backend/.deer-flow" + -v "${SKILLS_DIR}:/app/backend/skills" + -v "${KNOWLEDGE_DIR}:/app/backend/knowledge" + -v "${LOGS_DIR}:/app/backend/logs" +) + +if [[ -S /var/run/docker.sock ]]; then + DOCKER_ARGS+=(-v /var/run/docker.sock:/var/run/docker.sock) +fi + +DOCKER_ARGS+=("${IMAGE_NAME}") + +docker "${DOCKER_ARGS[@]}" + +echo "Started ${CONTAINER_NAME}" +echo "Backend: http://127.0.0.1:${BACKEND_PORT}" +echo "Config: ${CONFIG_DIR}" +echo "Data: ${DATA_DIR}" diff --git a/offline-backend-20260512/backend/后台离线部署说明.md b/offline-backend-20260512/backend/后台离线部署说明.md new file mode 100644 index 00000000..ae643277 --- /dev/null +++ b/offline-backend-20260512/backend/后台离线部署说明.md @@ -0,0 +1,403 @@ +# DeerFlow 后台离线部署说明 + +本文档只说明“后台服务”的离线部署,不包含前端。 + +## 1. 当前服务器文件位置 + +当前已经在服务器上准备好的部署目录是: + +```bash +/opt/deerflow-backend +``` + +主要文件和目录如下: + +```text +/opt/deerflow-backend/ +├── dist/deerflow-backend-offline.tar # 离线 Docker 镜像包 +├── start-backend-docker.sh # 启动脚本 +├── save-backend-image.sh # 镜像导出脚本 +├── build-backend-image.sh # 在线构建脚本 +├── config.yaml # 默认配置模板 +├── extensions_config.json # 默认扩展配置模板 +├── langgraph.json # LangGraph 配置 +├── .env # 环境变量文件,没有则启动脚本会创建空文件 +├── .deer-flow/agents/ # 默认智能体模板目录 +├── skills/ # 默认技能模板目录 +└── docker-data/ # 实际运行时挂载数据,启动后生成 +``` + +当前服务器已验证: + +```bash +cd /opt/deerflow-backend +./start-backend-docker.sh 8001 +``` + +健康检查: + +```bash +curl http://127.0.0.1:8001/health +curl http://127.0.0.1:8001/api/health +``` + +## 2. 客户现场更换部署目录 + +客户现场可以换任意部署目录,不需要改脚本。把整个 `deerflow-backend` 目录放到目标位置即可。 + +示例:客户现场部署到 `/data/deerflow-backend` + +```bash +DEPLOY_DIR=/data/deerflow-backend +mkdir -p "$DEPLOY_DIR" + +# 将交付的 deerflow-backend 目录内容放到 $DEPLOY_DIR +cd "$DEPLOY_DIR" +chmod +x *.sh +``` + +加载离线镜像: + +```bash +docker load -i dist/deerflow-backend-offline.tar +``` + +启动后台,端口可改: + +```bash +./start-backend-docker.sh 8001 +``` + +如果客户现场要用 `9001` 端口: + +```bash +./start-backend-docker.sh 9001 +``` + +脚本会自动把当前目录作为代码目录,把数据放到: + +```text +$DEPLOY_DIR/docker-data +``` + +## 3. 数据、配置、智能体、技能目录 + +启动后,实际可修改的文件都在宿主机目录,不在容器内部。 + +默认目录如下: + +```text +$DEPLOY_DIR/docker-data/config # 配置目录 +$DEPLOY_DIR/docker-data/.deer-flow # 运行时目录:智能体、用户数据、SQLite 数据 +$DEPLOY_DIR/docker-data/skills # 技能目录 +$DEPLOY_DIR/docker-data/knowledge # 知识库目录 +$DEPLOY_DIR/docker-data/logs # 日志目录 +``` + +配置文件位置: + +```text +$DEPLOY_DIR/docker-data/config/config.yaml +$DEPLOY_DIR/docker-data/config/extensions_config.json +$DEPLOY_DIR/docker-data/config/langgraph.json +$DEPLOY_DIR/docker-data/config/.env +``` + +智能体目录: + +```text +$DEPLOY_DIR/docker-data/.deer-flow/agents +``` + +技能目录: + +```text +$DEPLOY_DIR/docker-data/skills/public +$DEPLOY_DIR/docker-data/skills/custom +``` + +SQLite 聊天/checkpointer 数据库目录: + +```text +$DEPLOY_DIR/docker-data/.deer-flow/data +$DEPLOY_DIR/docker-data/.deer-flow/.deer-flow/data +``` + +注意:当前部署使用 **MySQL 业务库 + SQLite 聊天/checkpointer 库**,不使用 PostgreSQL。SQLite 运行时可能同时有以下文件,备份或替换时要一起处理: + +```text +deerflow.db +deerflow.db-wal +deerflow.db-shm +``` + +## 4. 客户现场加载智能体和技能 + +推荐流程: + +1. 先启动一次,让目录自动生成。 +2. 停止容器。 +3. 复制客户的智能体、技能、SQLite 聊天库文件,并配置 MySQL 地址。 +4. 再启动容器。 + +命令示例: + +```bash +cd "$DEPLOY_DIR" +./start-backend-docker.sh 8001 +docker rm -f deerflow-backend +``` + +复制智能体: + +```bash +mkdir -p "$DEPLOY_DIR/docker-data/.deer-flow/agents" +cp -a /path/to/customer-agents/. "$DEPLOY_DIR/docker-data/.deer-flow/agents/" +``` + +复制技能: + +```bash +mkdir -p "$DEPLOY_DIR/docker-data/skills/public" +mkdir -p "$DEPLOY_DIR/docker-data/skills/custom" + +cp -a /path/to/customer-skills/public/. "$DEPLOY_DIR/docker-data/skills/public/" +cp -a /path/to/customer-skills/custom/. "$DEPLOY_DIR/docker-data/skills/custom/" +``` + +重新启动: + +```bash +cd "$DEPLOY_DIR" +./start-backend-docker.sh 8001 +``` + +如果只更新技能或智能体,不换数据库,也可以复制后直接重启: + +```bash +docker restart deerflow-backend +``` + +## 5. 修改或替换 SQLite 聊天/checkpointer 数据库 + +SQLite 只保存聊天运行时、LangGraph checkpointer、部分本地运行数据。业务表使用 MySQL,MySQL 地址见第 6 节。 + +最稳的方式是停容器后整体替换运行时目录: + +```bash +cd "$DEPLOY_DIR" +docker rm -f deerflow-backend + +mv docker-data/.deer-flow docker-data/.deer-flow.bak.$(date +%Y%m%d%H%M%S) +cp -a /path/to/customer-dot-deer-flow "$DEPLOY_DIR/docker-data/.deer-flow" + +./start-backend-docker.sh 8001 +``` + +如果只替换数据库文件,至少要处理这两个目录里的 `deerflow.db*`: + +```text +$DEPLOY_DIR/docker-data/.deer-flow/data/deerflow.db* +$DEPLOY_DIR/docker-data/.deer-flow/.deer-flow/data/deerflow.db* +``` + +操作示例: + +```bash +cd "$DEPLOY_DIR" +docker rm -f deerflow-backend + +mkdir -p docker-data/.deer-flow/data +mkdir -p docker-data/.deer-flow/.deer-flow/data + +cp -a /path/to/db-main/deerflow.db* docker-data/.deer-flow/data/ +cp -a /path/to/db-runtime/deerflow.db* docker-data/.deer-flow/.deer-flow/data/ + +./start-backend-docker.sh 8001 +``` + +不要在容器运行时直接覆盖 SQLite 文件,容易损坏 WAL 数据。 + +## 6. 修改配置和数据库配置 + +默认配置会在第一次启动时复制到: + +```text +$DEPLOY_DIR/docker-data/config/config.yaml +``` + +要改配置,改这个文件,不要进容器改。 + +本项目现场部署按 **MySQL + SQLite** 使用: + +- MySQL:业务表,例如用户、智能体、技能、任务、菜单、按钮、敏感词、轻应用等。 +- SQLite:聊天运行时和 LangGraph checkpointer,继续放在挂载出来的 `.deer-flow` 目录。 +- 不使用 PostgreSQL,不要配置 `database.backend: postgres`,也不要配置 `POSTGRES_URL`。 + +MySQL 地址主要改两个文件: + +```text +$DEPLOY_DIR/docker-data/config/config.yaml +$DEPLOY_DIR/docker-data/config/.env +``` + +`config.yaml` 里的数据库配置建议改成: + +```yaml +database: + backend: mysql + sqlite_dir: .deer-flow/data + mysql_url: $MYSQL_DATABASE_URL + pool_size: 20 + max_overflow: 20 + pool_timeout: 30 + echo_sql: false + +checkpointer: + type: sqlite + connection_string: .deer-flow/data/deerflow.db +``` + +然后在 `.env` 里写 MySQL 连接地址: + +```bash +MYSQL_DATABASE_URL=mysql+asyncmy://deerflow:密码@192.168.1.10:3306/deerflow?charset=utf8mb4 +``` + +把上面示例里的内容换成客户现场真实值: + +```text +deerflow # MySQL 用户名 +密码 # MySQL 密码 +192.168.1.10 # MySQL 服务器 IP +3306 # MySQL 端口 +deerflow # MySQL 数据库名 +``` + +如果 MySQL 就装在 Docker 宿主机这台服务器上,容器里不要写 `127.0.0.1`,建议写: + +```bash +MYSQL_DATABASE_URL=mysql+asyncmy://deerflow:密码@host.docker.internal:3306/deerflow?charset=utf8mb4 +``` + +如果密码里有特殊字符,需要做 URL 编码,常见示例: + +```text +@ 写成 %40 +# 写成 %23 +% 写成 %25 +空格 写成 %20 +``` + +MySQL 数据库建议提前建好: + +```sql +CREATE DATABASE deerflow CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +``` + +如果 MySQL 用户有建库权限,服务启动时也会尝试自动创建缺失的数据库。客户现场更稳的做法是先建库、导入 MySQL 备份,再启动后台。 + +如果客户现场已有 MySQL 备份,先在客户 MySQL 中导入: + +```bash +mysql -h 192.168.1.10 -P 3306 -u deerflow -p deerflow < deerflow_mysql.sql +``` + +SQLite 聊天库仍按第 5 节复制到: + +```text +$DEPLOY_DIR/docker-data/.deer-flow/data/deerflow.db* +$DEPLOY_DIR/docker-data/.deer-flow/.deer-flow/data/deerflow.db* +``` + +如果需要把数据目录单独放到别的位置,不需要改 `config.yaml`,启动时指定 `DATA_DIR` 更稳: + +```bash +cd "$DEPLOY_DIR" +DATA_DIR=/data/deerflow-backend-data ./start-backend-docker.sh 8001 +``` + +此时实际目录变成: + +```text +/data/deerflow-backend-data/config +/data/deerflow-backend-data/.deer-flow +/data/deerflow-backend-data/skills +/data/deerflow-backend-data/knowledge +/data/deerflow-backend-data/logs +``` + +如果客户现场部署目录变化,只需要换 `DEPLOY_DIR` 或把整包放到新目录;MySQL 地址仍然只改 `$DEPLOY_DIR/docker-data/config/.env`,SQLite/智能体/技能仍然跟着 `DATA_DIR` 走。 + +如果要改 `.env`,位置是: + +```text +$DEPLOY_DIR/docker-data/config/.env +``` + +修改配置后重启: + +```bash +cd "$DEPLOY_DIR" +docker rm -f deerflow-backend +./start-backend-docker.sh 8001 +``` + +## 7. 常用运维命令 + +查看容器: + +```bash +docker ps -a --filter name=deerflow-backend +``` + +查看日志: + +```bash +docker logs -f --tail 200 deerflow-backend +``` + +停止: + +```bash +docker rm -f deerflow-backend +``` + +重启: + +```bash +docker restart deerflow-backend +``` + +重新启动并换端口: + +```bash +cd "$DEPLOY_DIR" +./start-backend-docker.sh 9001 +``` + +检查接口: + +```bash +curl http://127.0.0.1:8001/health +curl http://127.0.0.1:8001/api/health +``` + +## 8. 交付清单 + +客户离线部署至少需要: + +```text +deerflow-backend/ +├── dist/deerflow-backend-offline.tar +├── start-backend-docker.sh +├── config.yaml +├── extensions_config.json +├── langgraph.json +├── .env +├── .deer-flow/agents/ +├── skills/ +└── 其他后台代码文件 +``` + +客户机器需要提前安装好 Docker。离线部署时不需要再下载 Python 依赖,依赖已经包含在 Docker 镜像里。