做离线部署这件事,最初是因为一个客户现场需要在内网环境部署一套基于大模型的文档处理平台,网络完全隔离。我一开始也以为只是把Dify的镜像备份过去再启动就行,真正动手才发现,插件、模型配置、向量数据库这些环节在离线环境里全都会变成新的坑。这篇文章把我从镜像打包到无网环境部署完整跑通的路径记录下来,包括Windows环境下的操作细节,希望能帮你少走几趟弯路。
1. 离线部署前必须想明白的四件事
1.1 离线环境缺的不是Dify代码,是整个运行时生态
Dify这种平台类应用,表面上看是跑在Docker容器里的一套服务,但它的依赖面比你想象的宽得多。除了API后端、Web前端这两个核心容器,它还依赖PostgreSQL、Redis、向量数据库(默认是Weaviate,也支持Qdrant等)、Sandbox沙箱、SSRF代理、插件守护进程、插件Nginx入口这些组件。任何一个镜像缺失,整个平台都起不来或者功能残缺。
在有网环境下,docker compose up -d会自动从Docker Hub拉取缺失的镜像,你几乎感知不到这些依赖的存在。但在离线环境里,一切都要提前打包带走。这还没完,Dify 1.x版本的插件机制是独立的,插件市场、插件包下载这些操作在无网环境下同样全部失效,所以插件部署也是离线方案里必须单独处理的一环。
我建议你动手之前先梳理清楚一件事:离线部署的本质是把"运行时依赖"完整搬到内网,而不是把"应用代码"搬过去。镜像、插件包、环境变量配置、模型Endpoint配置,这些都要纳入打包清单。
1.2 版本锁定:为什么不能随便拿一个最新版
很多人在离线部署时习惯直接git clone最新代码,或者从Release页面下载最新的zip包,然后在打包镜像。这个思路在有网环境没问题,但在离线环境下会埋一个隐患:Dify的API服务和插件守护进程之间存在版本兼容关系,如果plugin-daemon版本落后于API版本太多,会出现API容器反复报错、插件列表加载不出来的问题。
我这次用的是1.17.1版本。Dify官方在GitHub上每个版本对应一个tag,下载源码后进入docker目录,里面的docker-compose.yaml和.env.example已经把当前版本对应的所有镜像tag写好。你只需要严格按照这个版本文件去拉取镜像,不要自己去Docker Hub挑"看起来最新"的tag。
版本锁定的另一个好处是,docker-compose.yaml里各服务的镜像版本之间是经过官方测试的组合。比如1.17.1对应的plugin-daemon版本、sandbox版本、nginx版本都是固定的,你混搭一个别的版本可能表面看不出问题,但某个功能模块会随机抽风。
1.3 Windows这条路线上容易被人忽视的坑
很多人默认离线部署是Linux服务器的事,实际上Windows环境(不管是本地开发机还是Windows Server)也有真实需求。Windows上用Docker部署Dify,有几个特别容易踩的地方:
第一,Docker Desktop的WSL 2后端。如果电脑配置一般,建议手动限制WSL的内存和CPU占用,不然后台跑着PostgreSQL、Weaviate这些容器,风扇会狂转,系统响应也会变慢。可以在用户目录下的.wslconfig里设置memory=6GB或者8GB,看机器内存而定。
第二,文件挂载路径问题。Dify的.env文件里配置了DIFY_PORT、NGINX_PORT这些端口参数,Windows下要注意端口不被占用。另外docker-compose里如果有本地卷挂载,Windows下的路径转换偶尔会出幺蛾子,尽量使用相对路径,让compose文件自动处理。
第三,Windows命令行执行docker命令时的编码问题。某些Windows终端环境下,中文字符会在日志里显示成乱码,这不是部署失败,只是编码显示问题,不影响功能。
1.4 整体打包与部署的流程设计
离线部署的流程可以抽象成五步:
- 在一台有网环境的机器上,下载对应版本的Dify源码,整理docker-compose配置。
- 使用Docker拉取所有依赖镜像,打包导出为tar压缩包。
- 在联网环境下准备好需要的插件包(
.difypkg文件)。 - 把源码目录、镜像包、插件包一起拷贝到离线机器上。
- 在离线上导入镜像、配置
.env、启动服务、安装插件、配置模型。
听起来不复杂,但每一步里都有细节坑。我下面按顺序拆开讲,重点说实际操作中容易翻车的地方。
2. 有网环境下的镜像打包实战
2.1 从GitHub获取Dify源码的正确姿势
网上很多教程让直接git cloneDify仓库,但如果你只需要某个特定版本,用--branch参数指定tag更干净:
git clone --branch 1.17.1 https://github.com/langgenius/dify.git如果网络状况不理想,也可以在GitHub Release页面直接下载对应版本的Source code压缩包。源码下载完成后,进入dify/docker目录,这个目录是部署的核心。
cd dify/docker在这个目录下,你会看到docker-compose.yaml、.env.example、docker-compose.middleware.yaml等文件。.env.example是环境变量模板,后面部署时要复制一份改成.env再改参数。
2.2 精确收集镜像清单并拉取镜像
docker-compose.yaml里每一个服务节点下都有image:字段,这些就是需要打包的所有镜像。我建议用命令把所有镜像名提取出来,避免人工遗漏:
grep -E "^\s+image:" docker-compose.yaml | awk '{print $2}' | sort -u我整理了一下,Dify 1.17.1默认部署方案下,典型的镜像清单大致如下:
| 服务 | 镜像名 |
|---|---|
| API | langgenius/dify-api:1.17.1 |
| Web | langgenius/dify-web:1.17.1 |
| Plugin Daemon | langgenius/dify-plugin-daemon:0.2.x |
| Sandbox | langgenius/dify-sandbox:0.2.x |
| SSRF Proxy | langgenius/dify-ssrf-proxy:0.0.3 |
| Plugin Nginx | langgenius/dify-plugin-nginx:1.0.0 |
| Nginx | nginx:1.27-alpine |
| PostgreSQL | postgres:15-alpine |
| Redis | redis:6-alpine |
| Weaviate | weaviate:1.19.0 |
注意,具体版本要以你下载的源码里docker-compose.yaml为准,不要直接照抄我这里的tag数字。
收集好清单后,直接执行:
docker compose pull这个命令会按compose文件里的定义,把用到的所有镜像拉取到本地。这里有个我在Windows下遇到的典型报错:dify拉取镜像失败。
这个报错在有网环境都经常出现,原因多半是Docker Desktop的DNS解析问题或镜像源不稳定。我当时的处理办法是,检查Docker Desktop的Settings -> Docker Engine,把registry-mirrors配置成可用的镜像源,再docker compose pull重试。另外确认一下系统代理设置,Docker Desktop在Windows下会读取系统代理,如果代理配置有问题,镜像拉取也会中断。
2.3 批量导出镜像到tar包
镜像拉齐之后,用docker save把所有镜像打包成一个tar文件。
具体命令可以这样写:
docker save -o dify-images.tar \ langgenius/dify-api:1.17.1 \ langgenius/dify-web:1.17.1 \ langgenius/dify-plugin-daemon:0.2.x \ langgenius/dify-sandbox:0.2.x \ langgenius/dify-ssrf-proxy:0.0.3 \ langgenius/dify-plugin-nginx:1.0.0 \ nginx:1.27-alpine \ postgres:15-alpine \ redis:6-alpine \ weaviate:1.19.0如果你不想手敲,也可以用一条命令把所有本地打包的镜像全导出来:
docker save -o dify-images-all.tar $(docker images --format "{{.Repository}}:{{.Tag}}" | grep -E "langgenius|nginx|postgres|redis|weaviate")这里我强烈建议导出后用gzip压缩一下,因为Dify全量镜像打包出来一般有3~6GB,压缩完能明显减小体积,拷贝时省时间:
docker save 镜像列表 | gzip > dify-images.tar.gz导入时用docker load -i dify-images.tar.gz,docker load支持gzip压缩包格式,不需要先解压。
2.4 插件相关的镜像和文件也要一起带走
Dify 1.x版本架构里,插件不是一个"可选功能",而是核心能力(Agent、模型接入、工具调用全依赖它)。插件由plugin-daemon服务管理,但插件本身是运行时从市场下载的,不打包的话,离线环境里插件市场页面会一直加载失败。
所以有网环境阶段要做两件事:
第一,确认plugin-daemon和plugin-nginx这两个容器镜像已经包含在镜像清单里。这两个镜像负责插件的调度和网络入口,缺了它们,后台插件管理界面根本打不开。
第二,去Dify的插件市场把需要的插件包手动下载下来。插件市场地址在Dify后台的"插件"页面,选择你需要的插件后,通常能在详细页面找到下载.difypkg文件的入口。如果页面不方便下载,可以去插件对应的GitHub Release页面找.difypkg的附件。
我当时提前准备的是OpenAI兼容接口插件和一些常用的工具类插件,.difypkg文件下载后单独放到一个目录里,后面离线安装时直接上传就行。
3. 离线环境的部署与启动
3.1 传输前的文件整理
从有网机器往离线机器拷贝之前,最好把文件整理成清晰的目录结构,避免到了现场手忙脚乱:
├── dify-source/ # 源码压缩包或解压后的目录 │ └── docker/ │ ├── docker-compose.yaml │ ├── .env.example │ └── ... ├── dify-images.tar.gz # 镜像包 └── plugins/ # 提前下载的 .difypkg 插件包源码目录其实可以只保留docker相关的部分,其他文档、测试代码带过去也没意义,但为了保持完整性和方便后续排查,我一般整包拷过去。
传输介质方面,拷贝大文件优先用移动固态硬盘(U盘也行,但传输速度慢),拷完校验一下文件完整性,比如通过SHA256哈希值一致性来确认没有损坏,见下方命令(Windows下用certutil -hashfile 文件名 SHA256,Linux下用sha256sum)。
3.2 Docker Load导入镜像
离线机器上先确认Docker环境正常:
docker version docker info然后导入镜像包:
docker load -i dify-images.tar.gz如果镜像包较大,导入过程会持续几分钟甚至十几分钟。导入完成后务必检查镜像列表,确认所有需要的镜像tag都已出现:
docker images | grep langgenius这个步骤一定不要省。我在实际操作中遇到过导出的tar包在解压时中断的情况,docker load只导入了前面一部分镜像,后面由于网络传输丢包导致镜像文件结构不完整,启动容器时报错。老老实实核对完镜像列表再继续。
3.3 配置.env文件的关键参数
进入dify-source/docker目录:
cd dify-source/docker cp .env.example .env在Windows下,你可以直接用记事本打开.env去编辑,但我建议用VS Code这类现代编辑器改,避免因编码或换行符问题导致环境变量读取异常。记事本在Windows下默认编码可能是UTF-8 with BOM,某些环境变量解析器对BOM敏感,会出现奇怪的问题。
重点调整这几个参数:
SECRET_KEY:生成一个随机字符串,用于应用安全加密。
openssl rand -base64 42如果没有openssl,也可以用任意随机字符,但要足够长且包含大小写字母和数字。
POSTGRES_PASSWORD、REDIS_PASSWORD:数据库和缓存的密码,记得改掉默认值。如果你在离线环境没有特殊要求,保持默认也可以,但生产环境绝对要改。
VECTOR_STORE:默认是weaviate。如果你离线环境里需要中文知识库检索效果好一些,也可以考虑qdrant,但那就意味着镜像清单里还要加qdrant/qdrant,并且要改docker-compose.yaml里的向量库服务定义。这个改动在设计阶段就要想好,不要部署完再切换。
DIFY_PORT和NGINX_PORT:Dify默认用80端口对外访问,在Windows本机如果80被占用(比如IIS、其他Web服务),要改成8080之类的高位端口。
PLUGIN_DAEMON_URL:这个很关键。API容器需要通过这个URL访问plugin-daemon服务,Windows部署时一般保持http://plugin_daemon:5002这种docker内网地址,不要随便改成localhost,否则容器内访问不到插件守护进程。
3.4 启动服务与等待健康检查
配置好.env后,执行:
docker compose up -d第一次启动会创建容器并初始化数据库,耗时比较长,尤其Weaviate首次启动需要构建索引,可能要到两三分钟。用以下命令观察状态:
docker compose ps正常情况下,所有服务的STATUS列会是Up,部分依赖服务的健康状态字段会显示healthy。如果某个容器反复重启,先用docker compose logs <服务名>查日志。
启动完成后,浏览器访问http://localhost:<DIFY_PORT>/install,按流程设置管理员账号和密码。这里有个细节,初次访问必须走/install初始化页面,如果直接访问首页会跳转到登录页,但这时还没有管理员账号,容易误以为部署失败。
4. 插件离线部署:最容易卡住的环节
4.1 搞清Dify 1.x的插件机制
Dify 1.x把很多原先内置的能力拆成了插件,模型接入、Agent策略、工具调用等都由插件守护进程统一管理。插件市场的入口在后端的"插件"页面,在线环境下,这个页面会从远程拉取插件市场数据;离线环境下,市场列表会一直转圈加载不出来。
很多人在这一步误判为"Dify部署失败",其实平台本身是正常的,只是插件市场不通。解决思路很直接:提前在有网环境把插件包准备好,离线后走本地安装通道。
4.2 提前在联网环境准备插件包
插件包的格式是.difypkg。在联网环境部署过一版Dify的话,可以直接在插件市场上浏览插件,找到你需要的:
- OpenAI Compatible 系列(对接OpenAI、兼容各类兼容层)
- 各类模型API插件
- Agent策略插件
- 工具类插件(比如bing搜索、维基百科、计算器等)
下载.difypkg文件后,统一放到plugins目录。这里有个建议,你离线后可能用到什么插件,最好提前想全。因为离线机器一旦部署完,再想临时增加插件,只能让人重新在有网环境下载再传过来,很折腾。
4.3 离线安装插件包的标准流程
Dify后台的插件页面,在插件市场加载失败的情况下,会有一个"上传本地文件"或"通过Manifest安装"的入口。
操作路径大致是这样:
- 登录Dify后台,进入"插件"页面。
- 点击右上角"安装插件"按钮。
- 选择"文件"上传方式。
- 选中提前准备的
.difypkg文件,等待上传。 - 上传完成后,系统会解析插件包并安装,安装成功后插件显示在"已安装"列表。
这时候还需要启用插件并配置模型凭证。比如你上传了一个OpenAI兼容接口的模型插件,需要到"设置 -> 模型供应商"里把这个插件对应的模型Key配置好,才能在工作流里调用。
另一种方式是直接把插件包丢到plugin-daemon的数据目录下,看容器是否自动加载。但说实话,这种方式在Dify里并不稳定,不同版本的自动扫描目录名不一样,我在1.17.1测试下来,还是走管理后台上传最稳妥。
4.4 插件安装不生效的根因排查
插件上传成功但没生效,或者API接口调用时报"模型不存在",这种问题我遇到过几次,排查方向集中在三处:
一是PLUGIN_DAEMON_URL是否配置正确。可以通过查看API容器日志确认:
docker compose logs api | grep -i plugin如果日志里有类似connect to plugin daemon failed的报错,基本可以锁定是配置问题或容器启动顺序问题。
二是插件包是否与Dify版本兼容。某些第三方插件包的更新频率跟不上Dify版本,安装时提示"插件与当前平台不兼容"的话,不要强行装,去找插件作者的最新Release包。
三是检查sandbox容器网络策略。插件的工具调用有时会走沙箱执行,如果sandbox起不来,工具类插件的运行会报错,模型类插件则不受影响。
docker compose logs sandbox如果sandbox出现权限类错误,检查一下.env里的SANDBOX_API_KEY是否和docker-compose.yaml里的API_KEY一致。
5. Windows下部署过程中遇到的典型故障与排查链路
5.1 问题一:有网机器上拉取镜像反复失败
前面提到过,Windows下Docker Desktop拉取镜像失败最常见的三个原因:
- 网络解析不到镜像仓库域名,表现为
dial tcp: lookup registry-1.docker.io on <DNS>:53: no such host。 - 镜像源不稳定,表现为下载到一半连接重置。
- 代理配置异常,表现为所有拉取请求超时。
排查链路我建议这么走:
# 1. 测试能否解析仓库域名 nslookup registry-1.docker.io # 2. 测试到仓库的网络连通性 curl -I https://registry-1.docker.io/v2/ # 3. 查看Docker返回的详细报错 docker pull postgres:15-alpine确定是DNS还是代理的问题,再针对性处理。Registry Mirror可以配,但不同网络环境下镜像源的可用性不一样,别迷信某个源,多试几个。
5.2 问题二:API容器反复重启,日志显示插件守护进程不可达
巡检docker compose ps时发现api容器状态不正常,频繁Restarting。查看日志:
docker compose logs api | tail -100日志里出现类似[PluginDaemon] failed to connect to plugin daemon的记录,说明API服务在启动时尝试连接plugin-daemon失败。
排查链路如下:
- 确认plugin-daemon容器本身是否正常,
docker compose ps里plugin_daemon状态如果不是Up,先从这个容器查起。 - 确认
.env里PLUGIN_DAEMON_URL的地址。如果在Windows下误改成了http://localhost:5002,API容器内的localhost指的是它自己,不是宿主机,必然连不上。应该保持http://plugin_daemon:5002这种docker DNS名称。 - 确认容器间网络正常,可以进入api容器做一次网络连通性测试:
docker compose exec api curl http://plugin_daemon:5002/health如果curl返回JSON格式的健康状态数据,说明网络是通的,问题大概率出在配置解析上。
5.3 问题三:Windows防火墙拦截容器网络
这个坑比较隐蔽。在Windows上跑Docker Desktop时,容器对外映射的端口(比如80或8080)如果在本机浏览器都访问不了,但容器内访问正常,几乎可以确定是Windows防火墙拦住了Docker的虚拟网卡。
我的排查方式是打开Windows防火墙的"高级设置",在"入站规则"里找到Docker Desktop或者对应端口的相关规则,确认是否允许。本地调试时最简单粗暴的方法是临时关闭防火墙验证,如果是防火墙导致,再加放行规则。
还有一个容易忽略的点是Windows上的端口占用。如果.env里配置的NGINX_PORT=80,但本机有进程占用80端口,Nginx容器会启动失败,表现为一直在重启循环。查看日志能直接看到bind: address already in use之类的报错,改掉端口即可。
5.4 问题四:Weaviate起不来,知识库功能不可用
Weaviate是Dify默认的向量数据库,知识库的上传、检索都依赖它。离线下如果Weaviate容器起不来,Dify虽然能登录后台,但"知识库"页面创建数据集或上传文档时必然报错。
我遇到的情况是宿主机内存不足,Docker Desktop分配的内存不够,Weaviate启动后内存溢出被系统杀掉。重启容器时一直伴随重启。
这种情况下,先检查宿主机可用内存和Docker配置给WSL的内存上限,把内存配额调大。其次检查Weaviate容器的日志,看是内存问题还是磁盘问题。
docker compose logs weaviate | tail -505.5 通用排查手段:三件套
离线部署Dify的过程中如果遇到诡异问题,我建议先做这三件事:
docker compose ps看所有容器的当前状态。docker compose logs --tail=50 <服务名>看具体服务的最新日志。docker inspect <容器名>看容器的环境变量和挂载配置是否符合预期。
大部分问题都能在这三件套里找到线索,尤其不要一来就怀疑镜像缺失或文件损坏,先确认基础状态再判断问题层次。
6. 离线部署之后的收尾建议
平台能访问、管理员能登录、模型插件配置好、知识库能建立,这套离线部署就算真正跑通了。
我个人在多次重复这个流程后的经验是,不管你之前做过几次,每次开始前都把"镜像清单"和"插件清单"重新对一遍。版本升级后镜像列表会有变化,插件兼容性也会有调整,别拿旧经验直接套新版。
另外建议在离线机器上保留一份完整的部署记录,包括.env的关键配置、插件的版本、模型的Endpoint配置。这批机器通常长期不联网,等半年后需要扩容或升级时,这份记录的价值会非常明显。
如果你需要在更多节点部署,后面完全可以基于这套镜像包做一个私有镜像仓库,把tar导入到内网的Registry里,其他机器直接从内网Registry拉取。不过那属于另一个话题了,先把单机离线跑通,比什么都重要。