Skip to content

Docker 部署与运维

本章目标:

  1. 讲清 DeerFlow 的两套 docker-compose 编排(dev / prod)中 nginx、frontend、gateway、provisioner 四个服务各自的职责与差异。
  2. 拆解 deploy.sh / docker.sh 的 build / start / down 流程,以及沙箱模式如何决定 provisioner 是否启动。
  3. 掌握 nginx 反向代理的路由规则、K8s 沙箱 provisioner 的 Pod/Service 生命周期,以及部署规模(deployment sizing)的选型依据。

TL;DR

DeerFlow 用 nginx 作为唯一入口(默认端口 2026),按路径把 /api/langgraph/* 与其它 /api/* 转发到 Gateway(8001),/api/sandboxes 转发到 provisioner(8002),其余请求转发到 Next.js 前端(3000)docker/nginx/nginx.conf:43-240。生产用 scripts/deploy.sh(make up/down)跑 docker/docker-compose.yaml,开发用 scripts/docker.sh(make docker-start)跑 docker/docker-compose-dev.yamlscripts/deploy.sh:46 scripts/docker.sh:16。两个脚本都会从 config.yamlsandbox.use / provisioner_url 自动探测沙箱模式(local / aio / provisioner),只有 provisioner 模式才额外拉起 provisioner 容器scripts/deploy.sh:132-163。后端镜像是三段式多阶段构建(builder / dev / runtime),DooD 模式通过挂载宿主 Docker socket 让 Gateway 进程直接拉起沙箱容器backend/Dockerfile:1-101

Overview

DeerFlow 是 harness/app 双层的 LangGraph super agent。一次完整运行不只是「跑一个 Python 进程」:它需要前端 UI、Gateway API + 内嵌 agent runtime、可选的隔离沙箱(本地文件系统 / Docker / Kubernetes),还需要把这些组件统一到一个浏览器同源(same-origin)入口下,避免 CORS / CSRF 复杂度。

为什么要用 nginx 反向代理而不是让前端直连 Gateway?因为 DeerFlow 的鉴权依赖 HttpOnly Cookie 与 CSRF origin 校验。把前端(3000)和 Gateway(8001)收敛到 nginx 的单端口(2026)后,浏览器看到的就是同一个 origin,Cookie 与 CSRF 检查天然对齐;只有在拆分前后端或端口转发部署时,才需要显式配置 GATEWAY_CORS_ORIGINS 让 CORS 和 CSRF 保持一致,而不是在代理层放行所有 origindocker/nginx/nginx.conf:36-39

为什么沙箱要分三种模式?因为部署场景差异极大:本地评估只需 LocalSandboxProvider(无容器);单机隔离用 AioSandboxProvider + DooD(Docker-out-of-Docker,通过宿主 Docker socket 起沙箱容器);多用户/生产隔离则用 provisioner + Kubernetes(每个 sandbox 一个 Pod + NodePort Service)。deploy.sh / docker.sh 通过解析 config.yaml 自动判定模式,使同一套 compose 文件适配三种场景。

Architecture

生产编排定义在 docker/docker-compose.yaml,声明 nginx / frontend / gateway / provisioner 四个 service,前三个总是启动,provisioner 仅在 provisioner 沙箱模式启动docker/docker-compose.yaml:25-146。dev 编排 docker-compose-dev.yaml 结构相同但有关键差异:前端跑 pnpm dev(热重载,目标 dev),Gateway 走 dev-entrypoint.sh 在容器启动时 uv sync 并以 --reload 起 uvicorndocker/docker-compose-dev.yaml:86-181

Source 列表:

Components / Subsystems

nginx — 反向代理与统一入口

唯一对外暴露端口的容器,生产用 ${PORT:-2026} 映射到容器 2026,挂载 nginx.conf 为模板后 cp 成正式配置再启动docker/docker-compose.yaml:27-42。dev 编排额外做了 IPv6 兼容处理:若宿主无 /proc/net/if_inet6 则用 sed 删掉 listen [::]:2026 行再启动,避免无 IPv6 环境下 nginx 报错docker/docker-compose-dev.yaml:71-78depends_on 仅声明 frontend 与 gateway,不含 provisioner——这是有意为之,provisioner 上游用变量延迟解析,nginx 即使在 provisioner 未启动时也能正常启动docker/nginx/nginx.conf:193-205

frontend — Next.js 前端

生产用 frontend/Dockerfileprod target:builder 阶段 pnpm install + SKIP_ENV_VALIDATION=1 pnpm build,prod 阶段只拷贝预构建产物并 pnpm startfrontend/Dockerfile:31-50。容器内通过 DEER_FLOW_INTERNAL_GATEWAY_BASE_URL=http://gateway:8001 走 Docker 网络服务名访问 Gateway,生产还要求注入 BETTER_AUTH_SECRETdocker/docker-compose.yaml:54-58。dev 用 dev target,挂载 frontend/src 等源码目录并跑 pnpm run dev(WATCHPACK_POLLING=true 解决 Docker 卷文件监听问题)docker/docker-compose-dev.yaml:96-114

gateway — FastAPI Gateway API + 内嵌 agent runtime

核心容器,镜像来自 backend/Dockerfile。生产用三段式构建的最终 runtime 阶段(无 build-essential,镜像约小 200 MB 并减小攻击面),启动命令 uv run uvicorn app.gateway.app:app --port 8001 --workers ${GATEWAY_WORKERS:-4},默认 4 个 workerdocker/docker-compose.yaml:64-74 backend/Dockerfile:71-101。挂载项包括只读的 config.yamlextensions_config.jsonskills/,可写的 .deer-flow 数据目录,宿主 Docker socket(DooD,供 AioSandboxProvider 起沙箱容器),以及只读的 ~/.claude~/.codex 用于 CLI 自动鉴权docker/docker-compose.yaml:75-94extra_hostshost.docker.internal 映射到 host-gateway,保证 Linux 上也能访问宿主docker/docker-compose.yaml:110-111

dev 编排用 dev target(保留编译链)并以 dev-entrypoint.sh 为入口,把 .venv 与 uv cache 放进 Docker 命名卷,防止挂载整个 backend/ 时把镜像构建出的虚拟环境覆盖掉docker/docker-compose-dev.yaml:132-148

dev-entrypoint.sh — 开发容器启动脚本

从原先 compose 内联 command: 抽出的 POSIX sh 脚本(锚定 /bin/sh 因 alpine 基础镜像可能无 bash)。职责:① 从 UV_EXTRAS 解析 --extra X,并用 [A-Za-z][A-Za-z0-9_-]* 校验,防止 .env 中的 shell 元字符注入 uv sync;② uv sync --all-packages 让 workspace 成员的 extra(如 harness 的 postgres extra)被安装;③ 自愈:首次 sync 失败则重建 .venv 重试一次;④ 以 exec 交棒给带 --reload 的 uvicorn,使其成为容器 PID 1docker/dev-entrypoint.sh:38-86。该脚本以只读卷挂载,改脚本后 make docker-restart 即可生效,无需重建镜像docker/docker-compose-dev.yaml:134-136

provisioner — Kubernetes 沙箱供给器(可选)

仅在 provisioner 沙箱模式启动的 FastAPI 服务(端口 8002),镜像基于 python:3.12-slim,装 fastapi/uvicorn/kubernetesdocker/provisioner/Dockerfile:1-29。挂载宿主 ~/.kube/config 为只读,通过 K8s API 为每个 sandbox_id 创建一个 Pod(镜像默认 all-in-one-sandbox:latest)+ 一个 NodePort Service,后端经 host.docker.internal:{NodePort} 直连沙箱docker/provisioner/app.py:301-400。Pod 资源限额:requests 100m CPU / 256Mi / 500Mi 临时盘,limits 1000m CPU / 1Gi / 500Midocker/provisioner/app.py:347-358。卷支持 hostPath 与 PVC 两种:生产建议设 SKILLS_PVC_NAME / USERDATA_PVC_NAME 用 PVC,避免节点故障导致数据丢失,PVC user-data 用 subPath: deer-flow/users/{user_id}/threads/{thread_id}/user-data 隔离docker/provisioner/app.py:243-298。容器带 healthcheck:每 10s curl http://localhost:8002/healthdocker/docker-compose.yaml:142-146

⚠️ provisioner 持有宿主 K8s 集群的完整访问权(挂载了 kubeconfig),且默认用 hostPath 挂载宿主目录进沙箱 Pod。仅应在可信环境运行,生产优先 PVC 卷docker/provisioner/README.md:329-339

Data Flow

make updeploy.sh 的 build 与 start 阶段流程:

一个浏览器请求经 nginx 的路由判定(longest-prefix / 正则匹配):

速查表

维度开发(dev)生产(prod)Source
编排文件docker-compose-dev.yamldocker-compose.yamlscripts/docker.sh:16 scripts/deploy.sh:46
入口命令make docker-start / docker.sh startmake up / deploy.shMakefile:156-157 Makefile:178-179
compose 项目名deer-flow-devdeer-flowscripts/docker.sh:16 scripts/deploy.sh:46
frontend 构建 targetdev(pnpm dev 热重载)prod(预构建 pnpm start)docker/docker-compose-dev.yaml:91 docker/docker-compose.yaml:49
gateway 启动dev-entrypoint.sh(uv sync+--reload)uvicorn --workers ${GATEWAY_WORKERS:-4}docker/docker-compose-dev.yaml:132 docker/docker-compose.yaml:74
网络deer-flow-dev(子网 192.168.200.0/24)deer-flow(bridge)docker/docker-compose-dev.yaml:189-194 docker/docker-compose.yaml:147-149
服务容器端口对外端口上游路由Source
nginx2026${PORT:-2026}唯一入口docker/docker-compose.yaml:30-31
frontend3000不直接暴露nginx /(非 API)docker/nginx/nginx.conf:222-239
gateway8001不直接暴露nginx /api/langgraph/*/api/*/health/docsdocker/nginx/nginx.conf:43-219
provisioner8002不直接暴露nginx /api/sandboxesdocker/nginx/nginx.conf:196-205
sandbox Pod(K8s)8080NodePort 自动分配provisioner 经 K8s API 创建docker/provisioner/app.py:320-400
沙箱模式触发条件(config.yamlsandbox.use)provisioner 是否启动Source
localdeerflow.sandbox.local:LocalSandboxProvider 或未匹配 AIOscripts/docker.sh:50-60
aiodeerflow.community.aio_sandbox:AioSandboxProvider 且无 provisioner_url否(DooD 用宿主 Docker)scripts/deploy.sh:154-162
provisioner含 AIO provider 且配置了 provisioner_urlscripts/deploy.sh:154-159

部署规模(deployment sizing)建议(来自 README,适用于 DeerFlow 本身,自托管 LLM 需另行评估):

部署目标起步配置推荐配置说明Source
本地评估 / make dev4 vCPU / 8 GB / 20 GB SSD8 vCPU / 16 GB单开发者或单轻量会话;2 vCPU/4 GB 通常不够README.md:213
Docker 开发 / make docker-start4 vCPU / 8 GB / 25 GB SSD8 vCPU / 16 GB镜像构建、bind 挂载、沙箱容器需更多余量README.md:214
长跑服务器 / make up8 vCPU / 16 GB / 40 GB SSD16 vCPU / 32 GB共享使用、多 agent 运行、报告生成、重沙箱负载README.md:215

CPU/内存持续打满时,优先减少并发 run 数,再升一档配置;持久化服务器推荐 Linux + DockerREADME.md:217-219

Configuration

配置项文件/位置作用安全标注Source
${PORT:-2026}docker-compose.yamlnginx 对外端口docker/docker-compose.yaml:31
GATEWAY_WORKERSdocker-compose.yamluvicorn worker 数(默认 4)按 sizing 调整,不宜盲目调大docker/docker-compose.yaml:74
BETTER_AUTH_SECRETdeploy.sh / compose前端 auth/session 安全⚠️ 生产必填;脚本自动生成并以 chmod 600 持久化到 .better-auth-secret,勿提交仓库scripts/deploy.sh:115-128
~/.claude / ~/.codex 挂载docker-compose.yamlCLI 自动鉴权只读挂载(read_only: true),仍属敏感凭据,谨慎使用docker/docker-compose.yaml:83-94
docker.sock 挂载docker-compose.yamlDooD 起沙箱容器⚠️ 等同宿主 root 权限,仅可信环境;非 local 模式 deploy.sh 会校验 socket 存在docker/docker-compose.yaml:81 scripts/deploy.sh:233-241
nginx SSE 配置nginx.confproxy_buffering off + 600s 超时长跑/流式响应必需,关闭缓冲避免 SSE 卡顿docker/nginx/nginx.conf:32-64
上传体积上限nginx.conf/api/threads/*/uploads 限 100M大文件上传路由单独放宽 client_max_body_sizedocker/nginx/nginx.conf:123-136
K8S_API_SERVERprovisioner env覆盖 kubeconfig 的 API server 地址kubeconfig 用 localhost 时容器内不可达,需改 host.docker.internaldocker/docker-compose.yaml:134
SKILLS_PVC_NAME / USERDATA_PVC_NAMEprovisioner env生产用 PVC 替代 hostPath⚠️ hostPath 在节点故障时丢数据,生产优先 PVCdocker/provisioner/app.py:243-298

nginx 关键点:用 set $gateway_upstream gateway:8001 这类变量让服务名在请求时(而非启动时)解析,避免容器重启换 IP 后上游变陈旧;/api/sandboxes 同理用变量延迟解析 provisioner,使 provisioner 缺席时 nginx 仍能启动docker/nginx/nginx.conf:26-29 docker/nginx/nginx.conf:194-198/api/ catch-all 块兜底所有未被更具体 location 覆盖的 API(如 /api/v1/auth/*),靠 nginx 的最长前缀匹配保证显式块仍优先docker/nginx/nginx.conf:207-219

Common Pitfalls / Tips

  • 容器内不要用 localhost 访问 Gateway:IM 渠道运行在 gateway 容器内,localhost 指向容器自身。compose 已注入 DEER_FLOW_CHANNELS_LANGGRAPH_URL=http://gateway:8001/apiDEER_FLOW_CHANNELS_GATEWAY_URL=http://gateway:8001 用服务名访问docker/docker-compose.yaml:102-103
  • docker compose down 报变量未设置警告:deploy.sh down 已为 down 路径补齐 DEER_FLOW_HOME 等占位默认值,使 compose 能解析卷定义不报警scripts/deploy.sh:167-177
  • dev 模式 .venv 被宿主空目录覆盖:dev 编排把 gateway-venv 命名卷挂在 /app/backend/.venv 上,保留镜像构建时的虚拟环境,否则挂载整个 backend/ 会用宿主空目录遮盖它docker/docker-compose-dev.yaml:137-140
  • macOS/Docker Desktop 下 uv 软链失败:dev 编排用 Docker 命名卷 gateway-uv-cache 替代宿主 bind 挂载做 uv cache,因 macOS 共享目录里 uv 创建软链会失败导致启动期 uv sync 崩溃docker/docker-compose-dev.yaml:145-148
  • provisioner kubeconfig 必须是文件不能是目录:Docker bind 挂载不存在的宿主路径会创建目录,provisioner 启动时会显式检查并报错 Kubeconfig path is a directorydocker/provisioner/app.py:158-162
  • docker.sh stop 会清理残留沙箱容器:dev 停止时除 compose down 外还调用 cleanup-containers.sh deer-flow-sandbox 清理 DooD 起的沙箱容器scripts/docker.sh:282-285
  • 受限网络可换镜像源:Dockerfile 支持 APT_MIRRORUV_INDEX_URLNPM_REGISTRYUV_IMAGEPIP_INDEX_URL 等 build arg 切换 apt/pypi/npm/ghcr 源backend/Dockerfile:6-23 frontend/Dockerfile:16-22

References

章节关系
02-快速开始与安装本章是其延伸:从安装入门进一步到 Docker 编排与生产运维细节
03-系统整体架构本章是其落地视角:把 harness/app/前端的逻辑架构映射到容器与网络拓扑
17-沙箱系统架构互补:本章讲 provisioner/DooD 的部署侧,该章讲沙箱系统内部架构

公司内部参考 · 由 claude-wiki-gen 基于源码自动生成的二次分析