ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DIFY源码二次开发:从改代码到构建自定义镜像全流程

DIFY源码二次开发:从改代码到构建自定义镜像全流程 改动 DIFY 源代码再自己构建镜像这件事我做过不止一次。最近又帮一个项目组把官方版 DIFY 改了内部流程再重新打包镜像顺手把整个操作链路又捋了一遍。DIFY 本身是目前很热门的大模型应用平台官方镜像用起来确实省心但到了一定程度就必须动源代码默认提示词不满足要求、界面文案要换成自己的品牌、内置工具要裁剪、模型接入逻辑要调整这些事情只在容器里改两下文件是治标不治本。这篇文章我就完整讲讲拿到 DIFY 源代码、找到要改的位置、修改后重新构建后端和前端镜像再用自定义镜像把整套服务拉起来。整个过程适合同样在做 DIFY 本地化部署、想深度二次开发的团队有一点 Docker 基础的人都能跟着做下来。1. 为什么不能直接改容器而要回到源代码构建镜像1.1 容器改动的“一次性陷阱”很多人第一次部署完 DIFY遇到需要调整的地方下意识会进容器直接改代码。做法大概是docker exec -it dify-api bash进到容器里的/app/api目录找到某个 Python 文件用 vim 改两行然后重启容器。当时看确实生效了但只要这个容器被重新创建——比如执行docker compose down之后再来一次docker compose up -d或者镜像被重新拉取更新所有改动全部归零。这不是操作失误而是容器的天然属性容器只是在镜像之上叠加了一个可写层文件改动都落在这个临时可写层里。容器删除这一层跟着销毁。虽然可以docker commit把容器固化成新镜像但这条路非常容易踩坑比如容器里残留了临时文件、日志、缓存导致镜像体积膨胀更致命的是你根本记不清自己到底改过哪些文件后续没法做版本回溯也没法对照官方升级。正确做法从一开始就应该是把改动回到源代码层面用修改后的代码重新构建出属于你自己的镜像。这样每一次改动都可追踪、可复现换一台服务器部署时直接用这个自定义镜像就行。1.2 官方镜像和源码版本本身就是一一对应的DIFY 社区版的所有镜像本质上都是用某个 release 分支或某个 tag 上的源码构建出来的产物。官方镜像langgenius/dify-api:1.10.0和 GitHub 上dify仓库的1.10.0tag 是对应的。所以一个很实用的思路是在源码仓库里先锁定官方发行版的 tag然后基于这个 tag 开自己的分支做修改。这样你随时可以知道“我的自定义版本相对于官方版本到底多改了什么”后期官方发布新版本时也能用git diff看到自己跟官方所有差异方便决定是否合并升级。1.3 哪些改动真正值得构建新镜像不是所有问题都要走到“改源码、构建镜像”这一步。先判断改动类型值得改源码的场景修改 Agent 默认提示词、修改前端品牌文案、增加自定义工具、修改模型供应商接入逻辑、裁剪内置依赖、固定某个依赖版本规避兼容性问题。不需要改源码的场景纯环境配置类参数比如修改docker-compose.yaml中的环境变量调整数据库连接、日志级别、并发数、模型 API Key这些直接在部署配置里改即可不用重新构建镜像。这里有一个常见的误区改环境变量能解决的事非要去改代码。比如日志级别代码里写的是从环境变量读取你改代码里那个默认值往往不生效因为环境变量的优先级高于代码默认值。改代码前先在源码里搜一下这个值是不是被环境变量覆盖了能省很多事。2. 准备环境一次干净的二次开发从这里开始2.1 环境清单要在本地完成“改源码 构建镜像”这套操作以我实际经验来看最少需要下面这些条件操作系统Linux 最省事Windows 强烈建议用 WSL2原生 Windows 跑 Docker 构建偶尔会遇到路径映射和权限问题。Docker20.10 以上版本构建镜像用 BuildKit 模式默认也是这个模式。Docker Composev2 版本直接用docker compose命令而不是老旧的docker-compose。Git用来拉源码、切分支、看改动。磁盘空间DIFY 后端镜像加前端镜像加上构建缓存建议预留 15GB 以上磁盘空间第一次构建往往会把所有基础镜像和依赖包都拉下来。检查环境是否就绪可以依次执行docker --version docker compose version git --version2.2 获取 DIFY 源码并锁定版本把一个开源项目的源码长期稳定地管理起来最忌讳就是随便 clone 最新版今天 main 分支上的代码和官方发版镜像可能根本不是一回事。所以我习惯固定到明确的 release tag 上。git clone https://github.com/langgenius/dify.git cd dify # 查看有哪些版本 git tag -l # 切到你需要的版本比如 1.10.0 git checkout 1.10.0 # 基于这个版本开一个自己的开发分支 git checkout -b custom-build锁定 tag 这一步很关键。DIFY 社区更新速度很快隔几周就是一个新版本依赖结构、配置文件位置、环境变量名都可能发生变化。如果 clone 了 main 分支构建时用的 Dockerfile 和部署时用的 docker-compose 配置可能是“开发中版本”跟你在 DIFY 官方文档里看到的部署方式对不上就会出现一堆莫名其妙的兼容问题。2.3 源码目录里我们需要关心的几个位置DIFY 仓库结构大概分为几大块api/后端服务Python Flask 写的所有业务逻辑、Agent 实现、工作流引擎、模型接入、知识库处理都在这里。web/前端服务Next.js TypeScript用户看到的控制台界面。docker/镜像构建和部署编排相关内容Dockerfile 和 docker-compose 配置在这里。sdks/各语言 SDK 示例构建镜像时用不到。后面所有改动我们其实只关心api/、web/、docker/这三个目录就够了。关于版本的细节我建议构建前先看一眼docker/目录下的 Dockerfile 和 compose 文件。不同版本基础镜像、依赖安装方式都可能调整先了解官方是怎么构建的后面遇到问题才有排查方向。3. 定位要修改的代码用一次真实改动掌握方法3.1 用“关键词搜索”代替“通读源码”DIFY 后端代码量不小第一次打开源码不要想着从头读到尾。我的经验是先明确要改什么功能或文案然后在整个源码目录里搜关键词定位到目标文件再顺着上下文一路看下去。比如我现在要把 Agent 内置的默认提示词改成适合自己业务场景的版本。这种提示词一般会以比较明显的字符串形式存在源码里那我就先搜典型的提示词片段。cd api grep -r You are --include*.py -n .如果不确定提示词是中文还是英文可以多搜几个关键词比如“你是一个”、“assistant”、“You are a helpful assistant”等等。命中之后打开对应文件找到那段字符串所在的位置确认它确实是 Agent 默认提示词的定义处。这里有一个实际操作中的心得所谓“默认提示词”可能在多个地方出现。有的在 Agent 的 prompt 模板里有的可能在 System Prompt 的默认配置里还有的在前端界面初始化应用时写入。你要找的是应用真正使用的那个建议修改之前先在源码里把同一关键词的所有命中位置都看一遍并用grep确认没有遗漏。3.2 一个完整的例子修改后端默认提示词假设我们已经通过grep定位到一个 Python 文件里面有一行类似这样的内容DEFAULT_AGENT_PROMPT You are a helpful assistant.现在我要把它改成项目需要的提示词DEFAULT_AGENT_PROMPT You are an expert in enterprise knowledge management. Please answer strictly based on the provided context.改完文件之后用git diff查看本次改动cd /path/to/dify git diff修改前后对比一目了然这比在容器里改完就忘强太多了。确认无误后提交到分支git add api/core/agent/xxx.py git commit -m feat: customize default agent prompt for enterprise scenario3.3 注意代码读取顺序和配置覆盖问题改完源码不代表一定生效。DIFY 后端大量配置项支持通过环境变量覆盖而且有些内容最终会存入数据库源码里的默认值只是“首次初始化”时使用的值。举个例子你拉起了 DIFY在界面上创建了一个应用这个应用的系统提示词很可能已经被写进数据库了。这种情况下你修改源码里的默认提示词只对新创建的应用生效存量应用不会自动更新除非你手动在应用设置里重新调整或者去数据库里更新对应记录。所以每次改代码之前先判断一下这个值到底是“代码里硬编码的全局常量”还是“首次写入数据库的初始值”又或者是“每次请求时动态拼接的模板”。三种场景的处理方式完全不同。这也是我见过很多人“改了源码重新构建镜像之后发现没效果”的根本原因之一。3.4 前端界面的修改路径如果你要修改的是前端界面比如把登录页的标题、Logo 文案替换成自己公司的品牌操作路径和后端不同但方法论一致进入web/目录搜索前端文案关键词。cd web grep -r Your App --include*.tsx --include*.ts -n .前端项目文件多、依赖重构建时间比后端更长但修改逻辑并不复杂。需要注意一点前端构建产物最终是静态文件会被 Nginx 或者对应容器直接托管。改完前端文件后重新构建前端镜像是必须步骤不是在容器里改一行文件就能完事的。4. 从源码到镜像后端与前端镜像的完整构建流程4.1 构建后端镜像代码改好接下来就是重头戏构建镜像。DIFY 的构建目录设计得很规整后端 Dockerfile 在api/Dockerfile前端 Dockerfile 在web/Dockerfile。构建后端镜像的命令是cd /path/to/dify docker build -f api/Dockerfile -t dify-api:custom-1.10.0 ./api我来拆解一下这个命令里每个参数的作用。-f api/Dockerfile指定使用哪个 Dockerfile 文件。-t dify-api:custom-1.10.0给新镜像打标签dify-api是镜像名custom-1.10.0是 tag建议 tag 里带上对应版本号方便后续识别。“./api”是构建上下文build context也就是告诉 Docker 构建时能访问哪些文件。这里只把api/目录作为上下文不要图省事直接写成“.”否则整个 DIFY 仓库都会被发送给 Docker daemon构建准备阶段会慢很多而且可能把一些无关文件也带进上下文。后端镜像体积比较大因为要安装大量 Python 依赖。构建时间一般在几分钟到十几分钟不等取决于你的网络环境和机器性能。4.2 构建前端镜像前端镜像同理docker build -f web/Dockerfile -t dify-web:custom-1.10.0 ./web前端构建过程需要下载大量 npm 包第一次构建会比后端慢这是正常的。前端镜像的构建产物会包含打包后的静态文件以及一个轻量的 Web 服务器通常是 Nginx。这里可以顺便解释一下为什么我们经常说“构建缓存很重要”。无论是后端还是前端Docker 在构建过程中都会对每一层做缓存。如果你只改了api/core/agent/xxx.py这个文件那么 Docker 会复用之前所有没变化的构建层只重新执行从“COPY 代码”开始的后续层大大缩短构建时间。但如果你的构建命令里加了--no-cache就等于告诉 Docker 放弃全部缓存哪怕只改了一行代码也要把依赖重新装一遍。4.3 把自定义镜像写进 docker-compose 并启动镜像构建完成现在要让整套环境跑起来。打开 DIFY 的docker-compose.yaml找到 api 和 web 这两个服务定义。原本的配置大概是services: api: image: langgenius/dify-api:1.10.0 web: image: langgenius/dify-web:1.10.0把镜像引用改为我们刚构建的版本services: api: image: dify-api:custom-1.10.0 web: image: dify-web:custom-1.10.0然后执行docker compose up -d注意docker compose up会先检查本地是否有对应镜像如果没有才会尝试拉取。我们自定义镜像已经在本地存在所以它不会去拉官方镜像。启动完成后查看日志确认服务状态docker compose logs -f api再访问/install完成初始化或者直接登录控制台验证你的改动是否生效。4.4 镜像推送到私有仓库如果这套自定义版本要给团队其他人用或者要部署到多台服务器需要把镜像推到你们自己的私有镜像仓库比如 Harbor、Registry 或者云厂商的容器镜像服务。docker tag dify-api:custom-1.10.0 registry.example.com/dify/dify-api:custom-1.10.0 docker push registry.example.com/dify/dify-api:custom-1.10.0服务器上只需要把 docker-compose 里的 image 改成私有仓库地址拉取启动即可。这样整个二次开发产物就真正变成了可分发、可复用的资产。5. 构建过程中的常见坑与排查技巧5.1 构建速度很慢甚至反复失败DIFY 后端依赖大量 Python 包前端依赖大量 npm 包第一次构建慢是正常的但如果出现反复卡住或超时就要考虑换依赖源。国内环境下可以直接把默认源切换成国内公共源不涉及任何环境变量之外的改动。后端构建临时改 pip 源可以在api/Dockerfile里看到 pip install 部分加一个-i参数例如pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple前端构建时 npm 源可以这样处理在web/目录下临时配置npm config set registry https://registry.npmmirror.com不过直接在 Dockerfile 里改源会影响可维护性我更推荐的做法是理解官方 Dockerfile 使用的构建参数和安装阶段然后通过 build-arg 或临时环境变量的方式传递源地址。但很多项目图省事直接改了 Dockerfile也能跑通就看你自己权衡。5.2 改了代码构建后却没效果这个我前面提到过最常见的原因有几种改的值是从环境变量或数据库读取的代码里的默认值根本不会被用到。前端浏览器缓存了旧的静态资源看起来像是没生效强制刷新或者换个无痕窗口看下。默认提示词等初始化数据已经写入数据库存量数据不会自动读新默认值。构建时 Docker 缓存了旧代码层。确认代码确实在构建上下文里并且改动文件的时间戳正常。排查建议先在源码根目录用grep确认你要改的字符串是写死的还是通过变量/环境变量引入的。确认之后修改时在代码里加一行临时日志输出构建启动后看日志最快判断改动有没有真正进入运行环境。5.3 前后端版本不匹配导致白屏或接口报错DIFY 的 api 和 web 两个服务是分开构建的如果后端代码改了接口结构、环境变量名而前端没有同步适配就可能出现页面打不开、接口返回 500 这类问题。最典型的场景是你只改了后端源码但 docker-compose 里 web 服务用的还是官方旧镜像。新旧版本之间接口不兼容前端自然跑不起来。解决方案就是确保每次自定义构建时前后端版本保持同一个 tag 基线改了后端就同步确认前端版本对着改了前端也要确认后端能对得上。5.4 基础镜像拉取失败和构建上下文过大构建 DIFY 这样的大项目基础镜像是否能在本地命中很关键。官方 Dockerfile 一般会固定基础镜像的版本号比如python:3.10-slim、node:20-alpine这类。如果构建时卡在拉取基础镜像这一步可以先手动把基础镜像拉下来再执行构建。上下文过大是另一个隐蔽问题。构建命令里末尾的上下文路径不要随手写当前目录。DIFY 仓库里有很多历史文件、示例资源、SDK 代码你构建后端镜像只需要api/下的内容构建前端镜像只需要web/下的内容。上下文路径写对了不仅能减少构建准备时间还能避免一些无谓的缓存失效。5.5 个人习惯每次改动尽量集中、离散最后分享一个我自己执行这类二次开发时养成的习惯。每一次自定义改动尽可能做成一个小而清晰的 commitcommit message 写清楚“改了什么、为什么改、对版本有什么影响”。比如git add api/core/agent/xxx.py git commit -m feat: adjust agent default prompt - change default prompt to fit enterprise knowledge scenario - only affects newly created apps这个习惯在后期升级官方版本时非常有用。官方发一个新版本你可以回到主分支拉取新 tag然后在自己的分支上执行git rebase或git merge通过 diff 一眼看出哪些自定义改动需要重新适配哪些已经合并进官方版本冲突处理起来也有据可查。如果从头到尾只改不记录几个月后你自己都不知道这个镜像里跟官方版本差了多少东西那样部署到生产环境是有很大风险的。代码管理这件事在二次开发里从来都不是可选项而是必修课。
返回列表