主题
Docker 部署与运维
本章目标:
- 讲清 DeerFlow 的两套 docker-compose 编排(dev / prod)中 nginx、frontend、gateway、provisioner 四个服务各自的职责与差异。
- 拆解
deploy.sh/docker.sh的 build / start / down 流程,以及沙箱模式如何决定 provisioner 是否启动。- 掌握 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.yaml 的 sandbox.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 列表:
- 生产编排:docker/docker-compose.yaml:25-149
- 开发编排:docker/docker-compose-dev.yaml:15-195
- 后端镜像:backend/Dockerfile:1-101
- 前端镜像:frontend/Dockerfile:1-51
- provisioner 镜像/服务:docker/provisioner/Dockerfile:1-29 docker/provisioner/app.py:301-400
- nginx 路由:docker/nginx/nginx.conf:21-240
- 部署脚本:scripts/deploy.sh:1-274 scripts/docker.sh:1-358
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-78。depends_on 仅声明 frontend 与 gateway,不含 provisioner——这是有意为之,provisioner 上游用变量延迟解析,nginx 即使在 provisioner 未启动时也能正常启动docker/nginx/nginx.conf:193-205。
frontend — Next.js 前端
生产用 frontend/Dockerfile 的 prod 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.yaml、extensions_config.json、skills/,可写的 .deer-flow 数据目录,宿主 Docker socket(DooD,供 AioSandboxProvider 起沙箱容器),以及只读的 ~/.claude、~/.codex 用于 CLI 自动鉴权docker/docker-compose.yaml:75-94。extra_hosts 把 host.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 up → deploy.sh 的 build 与 start 阶段流程:
一个浏览器请求经 nginx 的路由判定(longest-prefix / 正则匹配):
速查表
| 维度 | 开发(dev) | 生产(prod) | Source |
|---|---|---|---|
| 编排文件 | docker-compose-dev.yaml | docker-compose.yaml | scripts/docker.sh:16 scripts/deploy.sh:46 |
| 入口命令 | make docker-start / docker.sh start | make up / deploy.sh | Makefile:156-157 Makefile:178-179 |
| compose 项目名 | deer-flow-dev | deer-flow | scripts/docker.sh:16 scripts/deploy.sh:46 |
| frontend 构建 target | dev(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 |
|---|---|---|---|---|
| nginx | 2026 | ${PORT:-2026} | 唯一入口 | docker/docker-compose.yaml:30-31 |
| frontend | 3000 | 不直接暴露 | nginx /(非 API) | docker/nginx/nginx.conf:222-239 |
| gateway | 8001 | 不直接暴露 | nginx /api/langgraph/*、/api/*、/health、/docs | docker/nginx/nginx.conf:43-219 |
| provisioner | 8002 | 不直接暴露 | nginx /api/sandboxes | docker/nginx/nginx.conf:196-205 |
| sandbox Pod(K8s) | 8080 | NodePort 自动分配 | provisioner 经 K8s API 创建 | docker/provisioner/app.py:320-400 |
| 沙箱模式 | 触发条件(config.yaml 的 sandbox.use) | provisioner 是否启动 | Source |
|---|---|---|---|
| local | 含 deerflow.sandbox.local:LocalSandboxProvider 或未匹配 AIO | 否 | scripts/docker.sh:50-60 |
| aio | 含 deerflow.community.aio_sandbox:AioSandboxProvider 且无 provisioner_url | 否(DooD 用宿主 Docker) | scripts/deploy.sh:154-162 |
| provisioner | 含 AIO provider 且配置了 provisioner_url | 是 | scripts/deploy.sh:154-159 |
部署规模(deployment sizing)建议(来自 README,适用于 DeerFlow 本身,自托管 LLM 需另行评估):
| 部署目标 | 起步配置 | 推荐配置 | 说明 | Source |
|---|---|---|---|---|
本地评估 / make dev | 4 vCPU / 8 GB / 20 GB SSD | 8 vCPU / 16 GB | 单开发者或单轻量会话;2 vCPU/4 GB 通常不够 | README.md:213 |
Docker 开发 / make docker-start | 4 vCPU / 8 GB / 25 GB SSD | 8 vCPU / 16 GB | 镜像构建、bind 挂载、沙箱容器需更多余量 | README.md:214 |
长跑服务器 / make up | 8 vCPU / 16 GB / 40 GB SSD | 16 vCPU / 32 GB | 共享使用、多 agent 运行、报告生成、重沙箱负载 | README.md:215 |
CPU/内存持续打满时,优先减少并发 run 数,再升一档配置;持久化服务器推荐 Linux + DockerREADME.md:217-219。
Configuration
| 配置项 | 文件/位置 | 作用 | 安全标注 | Source |
|---|---|---|---|---|
${PORT:-2026} | docker-compose.yaml | nginx 对外端口 | — | docker/docker-compose.yaml:31 |
GATEWAY_WORKERS | docker-compose.yaml | uvicorn worker 数(默认 4) | 按 sizing 调整,不宜盲目调大 | docker/docker-compose.yaml:74 |
BETTER_AUTH_SECRET | deploy.sh / compose | 前端 auth/session 安全 | ⚠️ 生产必填;脚本自动生成并以 chmod 600 持久化到 .better-auth-secret,勿提交仓库 | scripts/deploy.sh:115-128 |
~/.claude / ~/.codex 挂载 | docker-compose.yaml | CLI 自动鉴权 | 只读挂载(read_only: true),仍属敏感凭据,谨慎使用 | docker/docker-compose.yaml:83-94 |
docker.sock 挂载 | docker-compose.yaml | DooD 起沙箱容器 | ⚠️ 等同宿主 root 权限,仅可信环境;非 local 模式 deploy.sh 会校验 socket 存在 | docker/docker-compose.yaml:81 scripts/deploy.sh:233-241 |
| nginx SSE 配置 | nginx.conf | proxy_buffering off + 600s 超时 | 长跑/流式响应必需,关闭缓冲避免 SSE 卡顿 | docker/nginx/nginx.conf:32-64 |
| 上传体积上限 | nginx.conf | /api/threads/*/uploads 限 100M | 大文件上传路由单独放宽 client_max_body_size | docker/nginx/nginx.conf:123-136 |
K8S_API_SERVER | provisioner env | 覆盖 kubeconfig 的 API server 地址 | kubeconfig 用 localhost 时容器内不可达,需改 host.docker.internal | docker/docker-compose.yaml:134 |
SKILLS_PVC_NAME / USERDATA_PVC_NAME | provisioner env | 生产用 PVC 替代 hostPath | ⚠️ hostPath 在节点故障时丢数据,生产优先 PVC | docker/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/api与DEER_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_MIRROR、UV_INDEX_URL、NPM_REGISTRY、UV_IMAGE、PIP_INDEX_URL等 build arg 切换 apt/pypi/npm/ghcr 源backend/Dockerfile:6-23 frontend/Dockerfile:16-22。
References
- docker/docker-compose.yaml:25-149 — 生产编排定义
- docker/docker-compose-dev.yaml:15-195 — 开发编排定义
- docker/nginx/nginx.conf:21-240 — 反向代理路由
- scripts/deploy.sh:23-274 — 生产 build/start/down 脚本
- scripts/docker.sh:18-302 — 开发 init/start/stop/restart 脚本
- docker/dev-entrypoint.sh:21-86 — 开发容器入口脚本
- backend/Dockerfile:1-101 — 后端三段式镜像
- frontend/Dockerfile:1-51 — 前端 dev/prod 镜像
- docker/provisioner/app.py:243-400 — K8s 沙箱供给器实现
- docker/provisioner/README.md:133-339 — provisioner 配置与安全
- README.md:207-219 — 部署规模建议
- Makefile:156-183 — docker 相关 target
Related Pages
| 章节 | 关系 |
|---|---|
| 02-快速开始与安装 | 本章是其延伸:从安装入门进一步到 Docker 编排与生产运维细节 |
| 03-系统整体架构 | 本章是其落地视角:把 harness/app/前端的逻辑架构映射到容器与网络拓扑 |
| 17-沙箱系统架构 | 互补:本章讲 provisioner/DooD 的部署侧,该章讲沙箱系统内部架构 |