本文依据 langgenius/dify
main分支在 2026-10-25 公开的 Docker Compose 与环境配置。Dify 迭代很快,组件和配置随版本会变;文中的图表达的是职责和关键调用关系,不是逐函数验证过的调用图,部分组件需要显式启用,并非默认全部运行。
一、它早就不是一个"套壳 Web 应用"了
如果只看 Dify 最早的用法,它确实很像一个"给大模型加了一层可视化界面"的 Web 应用:浏览器里配 Prompt、选模型,后端调 LLM API,再把结果送回前端。最简单的聊天应用,四个方块就画完了:
flowchart LR
U[用户] --> W[Web 前端]
W --> A[API 服务]
A --> D[(关系数据库)]
A --> L[外部大模型 API]
但 AI 应用一旦从"聊天窗口"走向承载业务流程,请求—响应这个模型就开始不够用了:
- 知识库要上传、解析、切分、Embedding、建索引,同时还要支撑在线低延迟检索。
- Workflow 要处理节点依赖、条件分支、循环、并行,并持续往外推流式事件。
- 插件和工具要装第三方代码、调外部服务,甚至跑用户写的脚本。
- Agent 可能拿到文件操作、命令执行和更长生命周期的自主任务能力。
- 生产环境还要稳定性、弹性扩容、资源隔离、持久化、故障恢复和审计。
先说清楚一点:上面这条演进脉络是我从当前架构反推出来的,不是版本考古,不代表 Dify 真的依次发布过这些部署形态。
有意思的是 Dify 应对这些需求的方式。它没有把每个功能都拆成微服务,而是保留一个统一的 Python 业务核心,再把任务执行、插件执行、代码沙箱、Agent Runtime 和基础设施一块块剥出去。最后长成的形态,我会这样概括:
模块化单体 + 多进程角色 + 独立隔离执行服务 + 专用存储组件。
二、服务架构:一个业务核心,多种运行角色
服务拓扑
flowchart TB
Client[浏览器 / 应用客户端 / 外部系统] --> Gateway[Nginx 入口网关]
subgraph Presentation[接入层]
Web[Web / Next.js]
Gateway --> Web
end
subgraph Business[核心业务层]
API[API Server / Flask]
Workflow[Workflow Engine]
Knowledge[RAG / 知识库服务]
App[应用、会话、权限等业务模块]
API --> Workflow
API --> Knowledge
API --> App
end
Gateway --> API
Web --> API
subgraph Jobs[后台任务层]
Redis[(Redis / Broker 与缓存)]
Worker[Celery Worker]
Beat[Celery Beat]
Beat --> Redis
Redis --> Worker
end
API --> Redis
subgraph Runtime[独立执行服务]
Plugin[Plugin Daemon]
Sandbox[Code Sandbox]
Agent[Agent Backend]
AgentShell[Local Sandbox]
SSRF[SSRF Proxy]
AgentProxy[Agent SSRF Proxy]
Agent --> AgentShell
AgentShell --> AgentProxy
Sandbox --> SSRF
end
Workflow --> Plugin
Workflow --> Sandbox
API --> Agent
Worker --> Plugin
subgraph Storage[数据与存储]
SQL[(关系数据库)]
Vector[(向量数据库)]
Object[(文件或对象存储)]
end
API --> SQL
Worker --> SQL
Knowledge --> Vector
Worker --> Vector
API --> Object
Worker --> Object
Plugin --> External[模型 / 工具 / 外部 API]
Agent --> Plugin
读这张图要注意两点:Workflow Engine、RAG、应用管理都是 API 代码内部的逻辑模块,不是三个容器;箭头表示业务依赖,不代表每条线都是一次网络 RPC。api_websocket、KnowledgeFS 这类可选部署模式我没画进去。
每个服务管什么
| 服务 | 主要职责 | 为什么要单独存在 |
|---|---|---|
nginx | HTTP 入口、反向代理与路由 | 统一外部入口 |
web | 控制台、Workflow 可视化编辑、应用前端 | 展示层与业务处理分离 |
api | HTTP API、应用与工作流编排、权限、核心业务 | 领域逻辑只维护一份 |
worker | 文档处理、索引构建等后台任务 | 把耗时工作移出请求链路 |
worker_beat | 定时任务调度 | 定时触发维护和后台工作 |
redis | 缓存、Celery Broker、部分临时状态 | 解耦请求与后台消费 |
plugin_daemon | 插件生命周期与运行 | 插件代码不进 API 主进程 |
sandbox | Workflow 代码节点执行 | 承载高风险的代码运行 |
ssrf_proxy | 代理并约束受控组件的出站请求 | 管住出站访问风险 |
agent_backend | 独立的 Agent 执行后端 | Agent 运行边界独立化 |
local_sandbox | Agent 的 Shell 与工作空间 | 隔离命令和文件操作 |
agent_ssrf_proxy | Agent 沙箱专用出站代理 | 对 Agent 网络访问做更细的规则 |
| 关系库 / 向量库 / 文件存储 | 各自承载不同类型的数据 | 一致性、检索、大文件各司其职 |
这张表里最值得记住的一件事是:企业级架构不等于全部微服务。 Compose 里 api 和 worker 用的是同一个后端镜像,只是启动模式不同。业务模型集中维护,进程却能按 HTTP 压力和后台负载分别扩缩。这是一个非常务实的复杂度折中:拆运行时,不拆领域模型。
一次 Workflow 运行经过哪里
以一个带知识检索、LLM 和代码节点的 Workflow 为例:
sequenceDiagram
participant C as 客户端
participant A as API / Workflow
participant V as 向量数据库
participant P as Plugin Daemon
participant S as Code Sandbox
participant DB as 关系数据库
C->>A: 提交 Workflow 执行请求
A->>DB: 加载应用与 Workflow 配置
A->>V: 检索知识片段
V-->>A: 候选文本
A->>P: 调用模型或插件能力
P-->>A: 模型或工具结果
A->>S: 执行代码节点
S-->>A: 执行结果
A->>DB: 记录运行数据
A-->>C: 返回结果或流式事件
这里有一个常见误解:以为每次 Workflow 运行都丢给 Celery Worker 执行。不是。Workflow 引擎负责图调度:加载节点和连线、维护上下文、判断依赖是否满足、处理分支循环、收集输出和事件,它是 API 后端的一部分。Celery 主要承担文档处理、定时任务这类异步工作,具体走哪个队列取决于任务类型和配置。
RAG 其实是两套负载
flowchart LR
subgraph Offline[离线文档入库]
File[文档上传] --> Task[异步处理任务]
Task --> Parse[解析 / 清洗 / 分段]
Parse --> Embedding[Embedding]
Embedding --> Index[索引构建]
end
subgraph Online[在线检索]
Query[用户 Query] --> Retrieve[语义 / 关键词检索]
Retrieve --> Rank[重排 / 筛选]
Rank --> Context[上下文拼装]
Context --> LLM[LLM 生成]
end
Index --> Vector[(向量索引)]
Vector --> Retrieve
File --> Object[(文件存储)]
Parse --> Meta[(元数据)]
上传一份文档可能触发几分钟的解析和索引,而用户的一次检索要求的是毫秒到秒级的响应。吞吐型负载和延迟型负载被拆到不同的运行角色上,是 Dify 从"能跑"走向"可运营"的关键一步:两者终于不必共用同一套资源配置。
三、存储:按数据的性质分层,别把一切塞进一个库
存储是 Dify 架构里最容易被忽略、却最直接决定可靠性的部分。它面对的至少是四种性质完全不同的数据:事务记录、临时状态、向量索引、非结构化文件。
四类存储
| 存储 | 典型内容 | 核心要求 | 运维关注点 |
|---|---|---|---|
| 关系数据库(通常 PostgreSQL) | 应用定义、用户与租户、Workflow 配置、会话、运行记录、文档元数据 | 事务、一致性、可查询 | 备份、迁移、索引、连接池 |
| Redis | 缓存、Celery Broker、部分协调与临时状态 | 低延迟、高频读写 | 内存、过期与持久化策略、Broker 可靠性 |
| 向量数据库(Weaviate / Qdrant / Milvus / pgvector 等) | Embedding 与相似度检索索引 | ANN 检索性能 | 维度、索引重建、内存与磁盘 |
| 文件存储(本地 / OpenDAL 适配的后端) | 上传文件、解析产物、业务生成文件 | 大对象存取、生命周期 | 共享访问、权限、备份与清理 |
当前 docker/.env.example 里的默认值是:关系库 PostgreSQL(DB_TYPE=postgresql),向量库 Weaviate(VECTOR_STORE=weaviate),文件存储走 OpenDAL 的文件系统后端(STORAGE_TYPE=opendal、OPENDAL_SCHEME=fs)。这些是模板默认值,不是唯一选项。
关系数据和向量索引为什么要分开
两者不是替代关系。关系库里存的是"这个文件属于哪个数据集、切成了哪些分段、索引任务处于什么状态";向量库里存的是能快速比较语义相似度的向量。
麻烦在于它们之间没有一个统一事务。文件上传后,系统先存元数据,再异步解析、切分、生成向量。向量构建中途失败,文件记录不一定要回滚,但必须有明确的处理状态、重试和清理策略;反过来删除一份文档,元数据、向量和原始对象又要联动清理。
所以 Dify 的知识库路径本质上是一条跨多种存储介质的异步数据流水线,而不是单库事务。运维上要把索引重建、幂等处理、失败恢复和悬挂对象清理当成日常机制,而不是出事后的补救。
文件存储必须和计算节点解耦
默认的本地文件系统适合单机体验。一旦横向扩展,问题马上出现:多个 API、Worker 实例怎么访问同一份上传文件?容器重建后文件还在吗?异步任务被调度到另一台机器上,原始文件从哪读?
生产环境应该用 Dify 支持的外部对象存储或共享存储,让应用实例尽量无状态。文件内容和元数据要在权限、引用关系、生命周期、备份上一起设计,而不是把本地目录复制到每台机器上。
插件和 Agent 的存储常被漏掉
除了业务数据,plugin_daemon 有插件包、工作目录和运行缓存;Agent 沙箱有工作空间和 Home 目录。它们的安全属性和生命周期都跟普通上传文件不一样。
当前 Compose 里 local_sandbox 的工作空间挂的是独立 Volume。这便于管理执行空间,但要清醒:Volume 不等于隔离、加密或跨租户权限控制。 只要是多用户、多租户的自主执行场景,任务级目录隔离、清理策略、用户身份、配额和宿主机文件权限都得逐项验证。
存储落地清单
- 关系库、Redis、向量库、对象存储分别规划容量、可用性和恢复目标。一个数据库快照不是完整备份。
- 业务库优先保证可靠备份和恢复演练;向量索引要先确认能否由原文和 Embedding 重建,以及重建要多久、花多少钱。
- 给文件、分段、向量、插件包、Agent 工作空间都定好生命周期和删除策略,多租户场景尤其注意资源清理。
- 评估 Redis 同时做缓存和 Broker 的风险,别让缓存驱逐或内存压力影响任务投递。
- 高并发下盯住连接池、慢查询、向量检索 P95/P99、Worker 积压和文件存储错误率。
四、网络:重点不是"能连通",而是"谁不该连通"
我认为 Dify 网络设计里最有代表性的变化,是把不可信代码和 Agent 执行环境从普通内网里剥出来,再通过受限代理只开放必要的访问。
三种信任等级
- 入口域:Nginx 和必要的 Web/API 路由,对客户端暴露服务端点。
- 业务域:API、Worker、数据库、Redis、向量库、插件服务,承担主要业务协作。
- 受限执行域:Code Sandbox、Agent Local Sandbox,执行相对不可信的脚本和命令。
这三个域不该拥有同样的网络权限。沙箱如果能随意访问数据库、Redis、Docker 守护进程或云元数据接口,一段恶意代码就能绕过应用层的全部权限控制。
SSRF Proxy 在防什么
SSRF(服务端请求伪造)在 AI 工作流里比在传统 Web 里更值得警惕:用户可以配 HTTP 节点、填 URL、调外部工具,模型自己也可能生成要访问的地址。这些请求如果由有内网权限的服务直接发出,就可能打到本不该暴露的内部端点。
Dify 的 Compose 里有一个基于 Squid 的 ssrf_proxy,配合 HTTP_PROXY、HTTPS_PROXY 使用。环境模板里的 SSRF_PROXY_ALLOW_PRIVATE_IPS 默认留空,即默认拒绝访问私有地址,确实要访问某个内网 API 时再显式加白名单。
flowchart LR
Tool[HTTP 节点 / Sandbox 出站请求] --> Proxy[SSRF Proxy]
Proxy --> Check{地址与策略检查}
Check -->|允许| Public[允许的目标服务]
Check -->|拒绝| Deny[拦截请求]
但别由此推断"所有流量都被强制代理"。这是一个代理控制面,实际效果取决于:哪些进程真的走了代理、Docker 网络有没有旁路、HTTP 客户端是否遵守代理环境变量、宿主机和容器上有没有配防火墙。
Agent Local Sandbox:一条更硬的边界
Agent 的风险和代码节点不是一个量级。它会连续执行 Shell 命令、创建文件、下载资源,并在多轮之间保留工作状态。所以 Dify 给 Agent 单独配了三件套:agent_backend、local_sandbox、agent_ssrf_proxy。
最值得注意的是网络划分。官方 Compose 注释写得很明确:local_sandbox 不加入普通的 default 网络,只加入 agent_sandbox_network 和 local_sandbox_proxy_network。Agent Backend 通过受限执行接口和它通信,它要访问其他服务,必须走 Agent 专用的 Squid 代理,而这个代理的配置模板只放行特定的 Agent 接口和文件传输路径。
flowchart TB
API[API / Worker] --> Agent[Agent Backend]
Agent -->|受控 Shell API| Local[Local Sandbox]
Local -->|HTTP / HTTPS Proxy| Proxy[Agent SSRF Proxy]
Proxy -->|允许的 Agent 接口| Agent
Proxy -->|允许的文件 API| API
Local -.-x DB[(业务数据库)]
Local -.-x Redis[(Redis)]
Local -.-x Internal[其他内部服务]
图里的虚线叉号是预期的隔离边界,不是对所有协议、所有路径都验证过的结论。代理环境变量本身拦不住所有出站,生产部署仍然要检查容器路由、网络策略和实际可达性。
这背后的思路一句话就能说完:不因为沙箱要执行代码,就把整个内网交给它;只开放完成任务所必需的那几个接口。
生产环境怎么加固
在 Kubernetes 或独立 VPC 里,可以在官方配置之上再往前走:
- 入口只暴露必需的 HTTPS 端口,数据库、Redis、插件调试端口一律不对公网开放。
- API、Worker、存储和不同沙箱分属不同命名空间或网络策略,默认拒绝、按需放行。
- 出站统一走专用的 Egress Gateway 或代理,重点处理 DNS 重绑定、重定向、私网 CIDR、云厂商元数据地址和代理绕过。
- 跨主机、跨集群的服务通信用 TLS 和服务身份认证,内网同样最小权限。
- 定期做 SSRF 和网络隔离的回归测试,确认沙箱能访问哪些 IP、端口和 URL 路径。
这些是我推荐的企业安全措施,官方 Compose 并没有全部替你做好。
五、安全:把信任边界落到进程、网络和凭据上
代码执行不能和主 API 共享进程权限
Workflow 代码节点和 Agent Shell 命令是两个最高风险的执行入口。应用层做了输入校验,也不能把它们当普通函数调用来信任。Dify 把它们放进独立的 sandbox 和 local_sandbox,为资源限制、出站控制和独立扩容留出了边界。
但跑在容器里不等于天然安全。 沙箱真正的强度取决于 Linux 权限、Capabilities、Seccomp、只读文件系统、资源配额、宿主机挂载、镜像供应链,以及有没有用更强的虚拟化隔离。看到服务名叫 sandbox 就放心,是最危险的误判。
内部服务调用要有身份
API 与 Plugin Daemon、Agent Backend 之间用内部 API Key 或 Token 认证。环境模板明确写了 DIFY_AGENT_API_TOKEN 的示例值只能用于开发,生产必须更换,Agent Backend 那边也要配上匹配的 Token,Release Notes 里也记录了这一要求。
它防的是"进了容器网络就能随便调高权限内部服务"。但 Token 的价值完全取决于凭据管理:
- 不要把示例密钥带上生产,也不要把真实密钥提交进 Git。
- 用 Secret Manager、Kubernetes Secret 或类似机制注入,并定期轮换。
- API、插件、Agent、沙箱各用各的凭据,别让所有服务共享一把高权限密钥。
- 调试接口、内部管理接口和文件传输 API 单独做访问控制和审计日志。
插件就是供应链边界
插件带来了扩展性,也带来了第三方代码和依赖。独立的 Plugin Daemon 让插件和主业务进程分开,配置里也有插件大小、执行超时、签名校验(如 FORCE_VERIFYING_SIGNATURE)等参数。
但签名校验不等于代码审计,执行超时也替代不了沙箱化。 企业落地时要对插件来源、版本锁定、依赖漏洞、网络权限、文件权限和敏感凭据注入定统一标准,低信任来源的插件走单独的审核流程和运行环境。
多租户:有租户字段不等于隔离完成
Dify 面向多应用、多用户、多组织,租户隔离要端到端成立,至少得检查四件事:
- 关系数据查询始终携带租户 / 应用身份;
- 文件和向量检索按归属与授权范围过滤;
- 缓存键、任务上下文和日志不跨租户混用;
- 插件密钥、模型供应商凭据、Agent 工作目录互不泄露。
这是从架构风险推出来的审查项,不代表 Dify 的所有接口都已经通过了多租户渗透测试。
AI 特有的风险,网络代理管不了
网络和代码执行都隔离好了,AI 平台依然要面对 Prompt Injection、工具调用越权、知识库内容污染、敏感信息经由模型上下文泄露。代理只能限制请求能去哪,判断不了这次调用是否符合业务授权。 高风险工具还得叠加参数校验、业务权限检查、人工审批、最小权限凭据和审计记录。
六、从单机 Compose 到生产部署的五步
把官方 Compose 原样放上服务器,能撑住一部分低负载场景,但它不自带高可用,也不自带企业级安全。正式上生产,我会按这个顺序推进:
第一步,业务进程解耦。 API 和 Worker 分别水平扩容,给 CPU 密集的文档解析、Embedding 和普通后台任务设好队列与并发参数,Workflow 请求延迟单独监控。
第二步,状态外置。 数据库、Redis、向量库、对象存储交给有备份恢复能力的服务,确保 API/Worker 实例随时可替换,不会丢本地文件。
第三步,执行安全域隔离。 插件、代码执行、Agent Shell 各自设计网络边界、身份凭据和资源限额,限制出站,并实测是否存在直连内部数据库和云元数据接口的路径。
第四步,可观测与恢复。 为 API 延迟、Workflow 节点耗时、Celery 积压、向量检索、模型供应商错误率、插件异常、存储耗尽、被拒绝的出站请求建指标和日志链路,并演练数据库恢复、队列积压处理和索引重建。
第五步,变更治理。 固定镜像版本,跟踪官方迁移脚本和 Release Notes。Agent Runtime、插件 Daemon、向量库或数据库升级前先核对配置变化和兼容性,再灰度。
结语
Dify 值得研究,不是因为它堆了多少容器,而是它给不同性质的工作划了不同的边界:
- 同步业务和后台任务分开,用户请求和批量索引各自调度;
- 关系存储、向量检索、文件存储和缓存分开,一致性、检索效率和大对象存储各自优化;
- 业务进程和不可信执行环境分开,脚本、插件、Agent 操作可以施加不同的权限和资源控制;
- 普通内网和受限沙箱网络分开,有执行能力的组件不能直接摸到高价值内部服务。
所以我更愿意把 Dify 看成一套逐步强化的 AI 应用运行平台,而不是一个大模型聊天 Web UI。它真正的企业级价值在于可扩展和可隔离同时成立:新的 AI 能力能接进来,接进来之后还能被治理、观测和约束。
但也要说清楚:企业级不是 docker compose up 成功之后就自动拥有的属性。 高可用、租户隔离、凭据治理、审计、灾备、出站安全和供应链控制,都要结合自己的安全等级和部署环境,一项一项去实现、去验证。