简介:面向希望快速获取 Dify 官方安装包的开发者与运维学习者,这份压缩包提供从 GitHub 项目页面整理的工程主体,可用于本地环境验证、源码阅读与二次开发准备。包内共 2000 个文件,以 1337 个 Python 文件承载主要业务逻辑,辅以 366 个 JSON 配置、116 个 CSS 和 84 个 JavaScript 前端资源;同时包含 38 个 Markdown 文档、32 个 YAML 部署配置,以及少量 Shell、HTML、SQL 文件,压缩包约 20.25MB,覆盖前后端与配置文档等常见模块。已有 2982 人学习下载,侧面反映其在 Dify 入门与环境搭建场景中的参考价值。解压后可按目录结构快速识别核心代码、接口配置、样式脚本与说明文档,便于核对依赖关系、定位前后端入口、理解配置项含义,也适合作为排查安装问题、研究项目组织方式的对照材料。
1. 从一份仓库压缩包说起:dify 官方安装包到底装了什么
我见过不少人把 dify 官方安装包当成一个双击就能用的 exe,从 github 下载下来解压后翻遍目录找不到启动图标,于是断定这东西不好用。实际上你拿到的是一个 docker compose 编排包:里面没有可执行程序,只有一份docker-compose.yaml、一份.env.example、若干配置模板,加上一份写着镜像 tag 的说明。它真正做的事情,是把你机器上的六个容器(api、worker、web、db、redis、向量库)按依赖顺序拉起来,拼成一个能跑 LLM 应用的完整后端。
它能解决的是那种“模型能调通但业务拼不起来”的困境:提示词版本管理、知识库检索、工作流编排、对外 API 发布,这些零散环节被塞进一个控制台里。适合谁?手上有一台 4C8G 的机器、想在一两天内把 RAG 或 Agent 应用跑通验证的开发者,以及需要给团队搭一套内部试玩环境的运维。不适合指望零运维、直接买 SaaS 的人。
2. 拿到安装包先别急着起容器:三种下载形态与目录分层
下载这一环节最容易出的问题不是下载慢,而是下错了东西。同一个仓库页面上至少摆着三种形态的产物,长得很像,用途完全不同,选错了后面所有步骤都会别扭。
2.1 Release 压缩包、仓库克隆、Helm 清单的取舍
第一种是 Release 里挂的源码压缩包(Source code (zip))。严格说它不是“安装包”,只是某个 tag 时刻的仓库快照。好处是版本和镜像 tag 天然对齐,坏处是没有 git 历史,后续想 diff 配置只能重新下一份对比。
第二种是直接克隆仓库再切到某个 tag。这是我自己的常用做法,因为升级时能git diff看出.env.example加了哪些新变量——dify 每个大版本几乎都会往.env里塞新配置项,漏掉一个就可能在启动日志里看到一堆空指针。
第三种是给 Kubernetes 用的 Helm chart,放在仓库的helm/目录下,走helm install那一套。单机验证完全没必要上它,多副本、滚动升级、把数据库外置到托管实例时再考虑。选型上的判断标准很朴素:只有一个域名、一台机器、数据可以丢,就用 compose;有 SLA 要求、需要横向扩 worker,才上 chart。
2.2 解压后先确认这五个目录
不管是压缩包还是克隆,解开后的顶层结构是固定的,先扫一眼能省掉后面半小时的迷路。
| 目录 | 作用 | 部署时是否要动 |
|---|---|---|
docker/ | compose 文件、.env.example、nginx 与沙箱配置 | 必动,所有部署动作都在这里 |
api/ | Python 后端,含 Flask 入口与 Celery worker | 只读,二次开发才改 |
web/ | Next.js 前端控制台 | 只读 |
sdks/ | 各语言调用客户端 | 只读,写业务代码时参考 |
helm/ | K8s 编排清单 | compose 部署可忽略 |
如果你解压完发现根目录下直接就是api/、web/,没有docker/,那基本是下载时点了某个子目录的打包链接,重新下一份完整的即可。这一步的判断很干脆:找不到docker/docker-compose.yaml,就是包不对。
2.3 版本通道:main、tag 与 latest 镜像标签
仓库默认分支永远处在开发中状态,.env.example里的变量可能指向还没发布的镜像。生产环境我只认两样东西:一个明确的 tag,和一份锁死镜像 tag 的 compose 文件。
拉取指定 tag 的命令很直接:
# 只取指定 tag 的最新一次提交,避免把整个历史拖下来 git clone --depth 1 --branch <tag-号> <仓库地址> dify-src cd dify-src # 确认当前指向的提交与 tag 一致,防止浅克隆带来的错觉 git describe --tags--depth 1的作用是浅克隆,几秒钟就能完成;--branch后面填 Release 页面上那个标签名,注意不要带v前缀之外的任何修饰。git describe是给自己留的后悔药——升级出问题时,第一件事就是确认线上跑的是哪个 commit。
再看镜像标签。compose 文件里image: langgenius/dify-api:1.x.y这类写法,如果写成latest,那么docker compose pull拉到的可能是三天前刚推的开发镜像,行为了变了但版本号没变,排查时非常难受。我的习惯是下载完压缩包后先grep -n "image:" docker/docker-compose.yaml,把所有latest改成与安装包对齐的具体 tag,改完再往下走。
3. 改 .env 再起容器:六个必须调整的值与启动顺序
.env.example是给“跑得起来”准备的,不是给“用得下去”准备的。直接cp一份就up -d,本地能开页面,但只要你把地址换成域名或公网 IP,文件预览、API 回调、插件安装就会成片失效。
3.1 宿主机前置条件与端口规划
先把底座摸清楚。Docker Engine 建议 20.10 以上,compose 必须是 v2 插件形态(docker compose而不是docker-compose),否则 compose 文件里的depends_on条件语法直接报错。
资源上,官方给的最低配是 2C4G,但那是“能启动”的线。真实场景里 api + worker + 向量库 + 数据库一起吃内存,跑一次带重排的知识库检索,2C4G 会开始 swap,表现为回答首字延迟从 1 秒涨到七八秒。我的建议是 4C8G 起,磁盘留 20G 以上,因为镜像本体加上模型文件缓存很容易吃掉十几个 G。
端口方面,新版 compose 在前面挂了一个 nginx 做统一入口,宿主机只暴露一个EXPOSE_NGINX_PORT(默认 80)。如果你机器上 80 已经被别的服务占了,改这个变量就行,别去动容器内部的端口映射。数据库和 Redis 默认不对外暴露,这一点不要为了图方便改掉。
前置检查一条命令搞定:
docker version --format '{{.Server.Version}}' docker compose version # 查看 80 端口是否被占用,被占用就改 EXPOSE_NGINX_PORT ss -lntp | grep ':80 'docker version --format只取服务端版本号,比整段输出好读;ss -lntp里-l是监听态、-n不做 DNS 反解、-p显示进程,三件套一起用能一眼看出是谁占着端口。
3.2 .env 里必须改的六个值
进docker/目录,先把模板复制成正式配置,再逐项改。下面这六个值是我每次部署都会过一遍的,少改一个都要在后面还债。
cd docker cp .env.example .env # 1. 会话与凭据加密密钥,一旦上线就不要再变 openssl rand -base64 42 # 2. 首次访问 /install 时用于设置管理员的初始密码,装完可留空 # INIT_PASSWORD= # 3. 对外访问地址,前端控制台用 # CONSOLE_API_URL=https://dify.example.com # APP_WEB_URL=https://dify.example.com # 4. 文件访问基地址,知识库上传的文件靠它拼下载链接 # FILES_URL=https://dify.example.com # 5. 宿主机入口端口 # EXPOSE_NGINX_PORT=80 # 6. 上传体积上限,单位 MB,要和 nginx 的 client_max_body_size 对齐 # UPLOAD_FILE_SIZE_LIMIT=50openssl rand -base64 42生成的是 42 字节随机串,长度够长且不含 shell 特殊字符,直接贴进SECRET_KEY=后面即可。
重点说SECRET_KEY和FILES_URL这两项。前者是数据库里模型 API Key、插件凭据的加密盐,改了它,表里的密文就再也解不开,控制台会报“凭据解密失败”,而界面不会告诉你原因。后者必须是浏览器能直接访问到的地址;填localhost而用户从外网进来,知识库文档的预览和下载会全部返回 404,但上传本身是成功的,很容易误判成“文件没存进去”。
CONSOLE_API_URL和APP_WEB_URL在新版本里承担前端拼接口地址的职责,跨域问题十有八九出在这里——把域名换成 IP 访问却忘了同步改这两个值,浏览器控制台会刷一片 CORS 报错。
3.3 启动、看日志、初始化管理员
配置改完就可以拉镜像起服务了。第一次拉取视网络情况要几分钟到十几分钟,别中途 Ctrl+C。
cd docker # 后台启动全部服务,首次会拉取镜像并初始化数据库 docker compose up -d # 等 30 秒左右再看容器状态,全部应为 running 或 healthy docker compose ps # 只看 api 的启动日志,初始化失败的信息都在这里 docker compose logs -f apidocker compose ps里要重点看两列:STATUS 和 PORTS。如果db显示healthy而api一直restarting,九成是数据库还没起来 api 就抢跑,或者.env里的DB_PASSWORD与db服务的环境变量不一致。logs -f api的-f是跟随输出,看到Running upgrade之类的迁移日志刷完,再按 Ctrl+C 退出,不要直接关掉终端。
六个容器的启动关系是有先后的:db、redis、向量库最先,api等数据库健康检查通过后跑数据库迁移,worker依赖api完成迁移才能消费任务,web最后只负责静态资源与控制台页面。所以正确的观察顺序是 db → api → worker → web,反过来看日志只会看到一堆超时。
服务起来后浏览器访问http://<你的地址>/install,设置管理员邮箱和密码。如果这个页面返回 404 或者空白,先确认 nginx 容器在跑,再确认EXPOSE_NGINX_PORT和你访问的端口一致。装完后把.env里的INIT_PASSWORD清空并重启 api,否则任何人都能走到初始化页。
4. 模型接入与知识库跑通:一次最小可用验证
容器全绿只是及格线,能不能真的吐出回答是另一回事。这一章用一个最小闭环做验收:接一个模型、塞一份文档、发一次 API 请求,三步都通才算部署成功。
4.1 模型供应商的四个必填参数
控制台右上角进设置 → 模型供应商。如果你用的是公有云厂商,选对应图标填 Key 就行;多数人实际会走“OpenAI-API 兼容”这一类通用接入,因为它能把自建推理服务、第三方聚合网关都纳进来。
四个参数决定成败:
| 参数 | 含义 | 常见错误 |
|---|---|---|
| Base URL | 模型服务的根地址,通常要带/v1 | 少写/v1,报 404 |
| API Key | 鉴权密钥 | 前后带空格,报 401 |
| 模型名称 | 服务端注册的名字,区分大小写 | 填成展示名而非调用名 |
| 模型类型 | 对话 / 文本嵌入 / 重排 | 把嵌入模型填进对话槽位 |
模型类型这一项最容易被忽略。对话模型、Embedding 模型、Rerank 模型在系统里是三个独立的槽位,填错不会立刻报错,但知识库建索引时会拿对话模型去算向量,结果是检索永远返回空。设完之后一定要去“系统模型设置”里把默认的三个槽位都指过去,包括那个平时没人注意的“系统推理模型”。
填完保存时,控制台会发一次测试请求。这里失败先别怀疑安装包,用 curl 直接打一遍同样的地址和 Key,能快速区分是配置问题还是网络问题。
# 直接验证模型服务的可达性,绕开应用层 curl -sS -X POST "https://<你的模型服务>/v1/chat/completions" \ -H "Authorization: Bearer <API_KEY>" \ -H "Content-Type: application/json" \ -d '{"model":"<模型名>","messages":[{"role":"user","content":"ping"}],"max_tokens":8}'-sS的组合是静默但不吞错误,-d里把max_tokens压到 8,只为验证连通性,省流量也省时间。返回里能看到choices数组就说明服务端没问题,控制台还报错就去查FILES_URL、出站网络和容器内的 DNS。
4.2 知识库的分段与检索参数
知识库这块,参数选错的表现不是报错,而是“答非所问”,排查成本极高。新建知识库时有三个决策点。
第一个是分段模式。通用分段按固定长度切,实现简单,适合结构松散的说明文档;父子分段会把文档先切成大块再切成小块,检索命中子块、喂给模型的是父块,适合技术手册这种上下文依赖强的材料。我一般先用通用分段跑通,确认检索词能命中,再换父子分段做效果对比。
第二个是分段长度与重叠。分段长度默认 500 字符左右,重叠 50。中文场景下这个值偏保守,长句容易被拦腰切断,导致关键实体只出现在其中一个块里。文档以短句、条款为主时可以压到 300;以论述性长文为主时拉到 800 并保留 10% 重叠。
第三个是索引方式。高质量索引会调用 Embedding 模型逐块算向量,费用和时间都上去了,但语义检索效果明显;经济索引只做关键词倒排,适合数据量大、只做精确查找的场景。这里的判断标准是:你问的问题和文档用词是否一致。一致就选经济,不一致必须选高质量。
检索设置里还有两个数要盯住:TopK 和相似度阈值。TopK 默认 3~4,调大能提高召回但会把噪声一起喂进去,反而降低答案准确度;阈值卡在 0.5 附近比较稳,太高会出现“明明库里有文档却检索不到”的玄学现象,太低则什么垃圾块都往里塞。有 Rerank 模型的话务必打开混合检索加重排,这一步的收益比调分段长度大得多。
4.3 发布成 API 并用 curl 验证
应用编排好之后点“发布”,再到访问 API 页面创建密钥。这一步产出的 Key 只在创建时完整显示一次,丢了只能重建。
验证走阻塞式接口最省事:
curl -sS -X POST "https://<你的地址>/v1/chat-messages" \ -H "Authorization: Bearer app-<你的应用密钥>" \ -H "Content-Type: application/json" \ -d '{ "inputs": {}, "query": "这份文档里提到的部署前置条件是什么?", "response_mode": "blocking", "conversation_id": "", "user": "smoke-test-01" }'几个字段的含义值得展开:response_mode取blocking会等完整答案再返回,适合脚本验收;改streaming则走 SSE 分块下发,前端聊天框必须用这个。conversation_id首次传空串,服务端会在响应的conversation_id里回一个,把它带上再问就是多轮对话。user是业务侧的用户标识,用来做会话隔离和用量统计,随便填会污染数据,建议用稳定 ID。
inputs对应你在应用里定义的变量,如果定义了必填变量而这里传空对象,接口会直接报参数校验失败。这是新手最常见的 400 来源。
5. 排查清单:五个让部署翻车的细节
部署这套东西的坑有个共同特征:报错信息指向的位置,和真正的原因往往隔了两层。下面五条是我自己踩过、也在别人机器上复现过的。
5.1 起不来的时候先看这三处
现象一:容器全部 running,浏览器访问超时或者 502。原因通常不在应用层。一是EXPOSE_NGINX_PORT改成了 8080,但访问时还在敲 80;二是云主机安全组只开了 22;三是CONSOLE_API_URL写的是localhost,页面能加载但所有接口请求都打到用户自己的机器上。解决方式:先docker compose ps确认 nginx 的端口映射是0.0.0.0:端口->80/tcp,再在宿主机上curl -I http://127.0.0.1:<端口>,本机通、外网不通就是安全组问题,本机也不通就去看 nginx 容器日志。
现象二:api 容器反复重启,日志里是数据库连接失败。原因是.env里的DB_PASSWORD与db服务实际初始化的密码不一致,或者上次部署留下的数据卷还在、密码是旧的。数据库的密码只在数据卷首次创建时生效,改.env不会同步改库里的密码。解决方式:全新环境直接docker compose down -v清卷重来;已有数据的环境用ALTER USER改库内密码,别动数据卷。
现象三:代码执行节点、插件安装一直转圈然后失败。原因是sandbox和plugin_daemon这两个容器没起来,或者它们与 api 之间的内部密钥不一致。这类容器在docker compose ps里很容易被忽略,因为它们没有对外端口。解决方式:docker compose ps -a看有没有exited的,再看docker compose logs sandbox;如果宿主机是 SELinux 开启的发行版,还要确认数据卷挂载没有被安全策略拦住,表现为容器内写文件权限拒绝。
5.2 跑起来之后的三类异常
现象四:知识库文档上传成功,但预览、下载全是 404,应用回答里也引用不到原文。原因是FILES_URL填的是内网地址或localhost,服务端拼出来的下载链接浏览器访问不到。这个坑的迷惑性在于上传接口是服务端内部调用,所以上传永远成功,只有读取才失败。解决方式:把FILES_URL改成浏览器可达的完整地址(含协议和端口),改完重启 api 和 worker;已上传的文件不用重传,链接是运行时拼的。
现象五:换了 Embedding 模型之后,老知识库检索全空,新建的却正常。原因是不同 Embedding 模型的向量维度不同,旧索引是按旧维度建的,新查询向量根本没法比对。控制台不会报维度错误,只会安静地返回零条命中。解决方式:换模型必须重建索引,把原文档重新走一遍分段和索引流程;如果文档量大,先在测试知识库里验证新模型效果,再决定是否全量迁移。这也是我为什么坚持在.env备份里单独存一份“当前使用的 Embedding 模型名”——升级前对一眼,能省掉一次全量重建。
现象六:跑一段时间后回答越来越慢,docker stats里 worker 内存一路涨到宿主机上限。原因是 worker 并发消费任务时,每个任务都会加载一次模型相关的上下文,长期运行存在内存堆积。解决方式:给 compose 里的 worker 加--concurrency限制(默认值偏高),并在宿主机上配置restart: unless-stopped配合定期重启;更彻底的做法是把 worker 单独拆到另一台机器,通过共享的 Redis 和数据库连接。
6. 升级与回滚:把 SECRET_KEY 和数据库一起备份
升级这件事,出问题的概率和升级跨度成正比,而能不能救回来,取决于你有没有在pull之前做备份。dify 的 api 容器启动时会自动跑数据库迁移,迁移是单向的——降级不会自动回滚表结构,所以“重新拉旧镜像”这条退路,必须配合数据库备份才成立。
我现在的固定动作是先备份,再动镜像。备份清单只有四样,但缺一不可:
| 备份对象 | 命令 / 位置 | 丢失后果 |
|---|---|---|
| PostgreSQL 全库 | pg_dump导出到宿主机 | 应用、会话、知识库元数据全没 |
app/storage卷 | docker cp或卷快照 | 上传的原始文档丢失,索引无法重建 |
.env | 直接复制文件 | SECRET_KEY丢失,凭据全部解不开 |
| 向量库数据卷 | 卷快照 | 需重新灌库建索引,耗时但可恢复 |
数据库导出用一条命令就够,注意在容器内执行,避免版本不匹配:
# 全库导出,custom 格式便于选择性恢复 docker compose exec -T db pg_dump -U postgres -Fc postgres > /backup/dify-$(date +%F).dump # 文件卷同样要备,文档原文丢了索引就白建 docker run --rm -v docker_app_storage:/data -v /backup:/backup alpine \ tar czf /backup/storage-$(date +%F).tar.gz -C /data .-T是关掉伪终端,否则重定向出来的 dump 文件会混入控制字符,恢复时报格式错误;-Fc是 custom 格式,恢复时可以用pg_restore -t 表名只捞某张表。第二条命令用一个临时 alpine 容器挂载数据卷再打包,比docker cp更适合整个目录的归档,卷名用docker volume ls确认真实名称,前缀通常是 compose 项目名。
升级流程我固定成五步:备份 →git fetch切到新 tag → 对比.env.example补新增变量 →docker compose pull再up -d→ 打开控制台点一次知识库检索和一次应用对话。第四步之后docker compose logs -f api要盯到迁移日志结束,中途报错立刻停手,不要反复up。
回滚则是三件事:镜像 tag 改回旧值、up -d、把备份的库和卷恢复回去。这里有个血泪经验——只改镜像不恢复数据库,通常会在启动日志里看到列不存在的报错,然后误以为是镜像坏了,白白多折腾一轮。
从那以后我每次升级前都强制走一遍备份脚本,哪怕只是改一个环境变量,哪怕上次升级很顺利。习惯这东西平时看不出价值,出问题的那十分钟里它就是唯一的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取