1. 先在 Mac 上跑一套 Dify,值不值得折腾
第一次在 Mac 上把 Dify 完整跑起来,是在一台 16G 内存的 M 系列笔记本上。当时的需求很朴素:想试一个 RAG 的检索效果,用在线版本的话,文档得先传上去,改一次切分策略就得重来一遍,来回折腾十几轮之后,我放弃了在线版本,改成本地部署。原因很简单——本地这套环境里,docker/volumes/app/storage下面就是我自己的文件夹,想删就删,想重切就重切,不用考虑一次上传几十个 T 的文件对别人的服务有没有压力。
Dify 这个平台,本质上是把大模型应用开发里那些重复劳动给打包了:提示词编排、知识库检索、工作流、Agent、模型供应商接入、对外 API 发布。你在网页上拖几条线、填几个参数,它就能给你生成一个可以调用的接口。而本地部署这套环境,你拿到的是它能跑起来的最小完整集合:api、worker、web、db、redis、vector store(默认是 Weaviate)、nginx,新一点的版本还会多出plugin_daemon和sandbox两个容器。整套东西用一份docker-compose.yaml编排起来,在 Mac 上跑没有任何平台专属的难点,前提是你把几个坑提前认清楚。
这篇内容适合两类人看:一类是刚拿到 Dify 这个名字,想在自己电脑上把界面跑起来看看长什么样的新手;另一类是已经跑起来了,但卡在端口冲突、容器重启、接不上本地模型这些细节上的同学。我会从“为什么这么设计”讲到“出问题了怎么查”,把每一步的意图说清楚,而不是丢一串命令让你自己猜。
先把结论放前面:在 Mac 上跑一套 Dify,硬件门槛大概是 8GB 给 Docker 用、20GB 以上空闲磁盘。16G 内存的机器跑起来会紧张但不至于跑不动,32G 及以上会比较舒服。如果你机器只有 8G 内存,那我不建议你折腾本地部署,光是数据库和向量库就能把内存吃干净。
2. Mac 的前置条件:Docker Desktop 的资源配额与芯片差异
2.1 内存和 CPU 到底该分多少给 Docker
Docker Desktop for Mac 的默认资源分配经常被忽略。它默认可能只给虚拟机分了 2GB 内存和 2 核 CPU,这个配置跑单个容器还行,跑一整套 Dify 会直接卡在启动阶段——典型现象是weaviate或db容器起来之后反复被 OOM 杀掉,或者api容器在初始化数据库时超时。
打开 Docker Desktop 的 Settings,进 Resources 页面,我一般这么配:
| 项目 | 最低建议 | 舒适配置 | 说明 |
|---|---|---|---|
| CPUs | 2 核 | 4 核及以上 | 编译和向量检索都吃 CPU |
| Memory | 6GB | 8GB~12GB | 低于 6GB 大概率起不来 |
| Swap | 1GB | 2GB | 内存不足时的缓冲,别指望它救场 |
| Disk image size | 60GB | 100GB | 镜像加数据卷会越占越多 |
这里有个容易被忽略的点:Mac 上 Docker 跑在虚拟机里,你在 Activity Monitor 里看到的com.docker.backend进程占用的内存,就是这整台“虚拟机”的内存,它是整体分配的,不会因为你停掉几个容器就立刻还给宿主机。所以如果你打算长时间挂着 Dify 用,同时还要开浏览器、IDE、设计工具,那 8GB 分给 Docker 是比较稳的中间值。
改完配额需要点 Apply & Restart,Docker 会重启整个虚拟机,所有容器会跟着停掉,这点要提前知道,别在有任务跑的时候改。
2.2 Apple Silicon 与 Intel 机器的镜像架构差异
这是 Mac 用户最容易踩的第一个坑。Dify 的部分镜像早期只有linux/amd64架构,在 M1/M2/M3 这些 ARM 芯片的机器上直接拉取,Docker 会提示架构不匹配,或者干脆起不来。解决方式有两种:
第一种,走 Rosetta 模拟。在 Docker Desktop 的 Settings 里开启 “Use Rosetta for x86/amd64 emulation on Apple Silicon”,然后对应服务在 compose 文件里加上platform: linux/amd64。Dify 官方在较早版本的docker-compose.yaml里,就是通过注释掉的platform字段来提示 Apple Silicon 用户手动打开。
第二种,等官方出多架构镜像。新版本的镜像基本都支持 arm64 了,直接docker compose pull拉下来就是原生架构,性能比模拟好一大截,尤其是api服务处理请求时的响应速度。
怎么判断自己是不是踩了这个坑?跑docker compose ps,如果某个容器状态一直是Restarting或者Exited (139),再看docker compose logs <服务名>,里面出现exec format error或者qemu相关的报错,那就是架构问题,八九不离十。
提示:M 系列芯片优先确认镜像是否为 arm64 原生。用
docker image inspect <镜像名> --format '{{.Architecture}}'可以直接看到架构,返回arm64就是原生,返回amd64就是模拟跑的。
2.3 目录摆放与磁盘空间,别把数据卷留在默认位置
Dify 的 compose 文件里,数据是通过 bind mount 挂到源码目录下的docker/volumes/里的,包括数据库文件、上传的文档、向量库数据。这意味着这个目录的体积会随着你上传的知识库文件不断膨胀,而且它是你所有数据的“命根子”。
所以在git clone之前,我建议你先想清楚把代码放在哪。推荐放在用户目录下的一个固定路径,比如~/workspace/dify,不要放在 iCloud 同步目录里(同步进程会遍历volumes下成千上万的小文件,既慢又容易出冲突),也不要放在外接移动硬盘上(休眠重插之后挂载点变了,容器会全部起不来)。
磁盘占用方面,一套干净的 Dify 环境大概是这样分布的:
- Docker 镜像本身:约 5~8GB(包含 nginx、postgres、redis、weaviate、api、web、sandbox 等)
- 数据库空库初始化后:约 200MB,随使用增长
- 向量库数据:跟知识库文档量正相关,几百份文档大概几百 MB
- 应用上传的原始文件:跟文档大小正相关
我见过有人用 256G 的 Mac 跑这套东西,跑到最后是 Docker 的磁盘映像撑满了,整台机器开始告警。定期跑一下docker system df看看空间分布,是有必要的。
3. 从零拉起 Dify:一次完整的部署流程
3.1 拿到源码之后,.env里必须先改的几个值
把仓库拉下来,进入docker目录,第一步永远是复制环境变量模板:
git clone https://github.com/langgenius/dify.git cd dify/docker cp .env.example .env.env.example里有几十个变量,大部分不用动,但有几个关系到你能不能安全跑起来,必须改:
第一个是SECRET_KEY。这个值用于会话签名和敏感信息加密,模板里给的是一个示例值,直接拿去用等于把密码写在门口。生成方式很简单:
openssl rand -base64 42把输出粘进去就行。注意这个值一旦设定,后续所有已保存的加密配置都和它绑定,中途更换会导致已配置的模型密钥解不开,所以第一次就设好,之后别乱动。
第二个是EXPOSE_NGINX_PORT。默认是 80,而 Mac 上的 80 端口经常已经被别的东西占了,比如某些开发工具、本地web服务。我一般的做法是直接改成8080或者8000,省得后面还要去查是谁占着 80。
第三个是INIT_PASSWORD。这是初始化管理员账号时的一个校验密码,留空的话安装页面不会要求输入,设了的话安装时必须填对。如果你这台机器只有自己用,留空也行;如果同一局域网里有人能访问到,建议设上。
第四个是数据库和 Redis 的密码。模板里给的是difyai123456这种明显的占位符,本机自用问题不大,但如果你的 Mac 连着公司网络、别人能扫到端口,那还是改成随机串稳妥。
3.2docker compose up -d之后,机器上到底发生了哪些事
执行命令:
docker compose up -d这条命令看起来只有一行,背后的顺序其实挺多的。Docker 会先解析 compose 文件里的依赖关系,然后按顺序做这几件事:拉取所有镜像 → 创建网络ssrf_proxy_network和默认网络 → 创建数据卷或绑定目录 → 按depends_on的依赖顺序启动容器。
实际启动顺序大致是:db和redis先起 →weaviate、sandbox、ssrf_proxy起来 →api连上数据库做迁移 →worker起来消费任务队列 →web起来提供前端 →nginx最后起来做反向代理。新版本里plugin_daemon(插件守护进程)也会在这一串里,它负责管模型供应商插件和工具插件,重启它会让已安装的插件短暂不可用,这是正常现象。
第一次执行,镜像下载会花掉大部分时间。全部镜像拉完,视网络情况从十几分钟到半小时不等。这期间你可以用另一个终端窗口跑:
docker compose ps观察各个容器的状态从Created变成Starting再到Up。api容器第一次启动时会执行数据库迁移脚本,日志里会刷出一大串 alembic 的迁移记录,这是正常的,不要看到一堆输出就以为出错了。
3.3 首次访问与管理员账号初始化
所有容器Up之后,浏览器打开http://localhost:8080(端口按你.env里设的来)。如果页面能出来,说明 nginx 和 web 都通了。第一次访问会自动跳到/install路径,让你设置管理员邮箱和密码。
这里有个坑:如果你之前用过旧版本,.env里可能保留了CONSOLE_API_URL和APP_WEB_URL这两个变量,它们的值如果是空的,容器内部的请求拼接会出问题,表现为登录后页面一直转圈或者接口返回跨域错误。本地部署最省事的做法是把这两项留空,让系统自动推断;只有在配了域名、走反向代理的情况下才需要显式填写。
设置完账号,进入控制台,你第一眼看到的应该是「模型供应商」配置页。这时候什么都别急着配,先确认服务是健康的。
3.4 用哪几个命令确认服务真的健康,而不只是“起来了”
容器Up不代表服务可用。我习惯用三个维度交叉验证:
# 维度一:容器状态与健康检查 docker compose ps # 维度二:api 服务是否完成初始化 docker compose logs api | tail -50 # 维度三:数据库能否正常查询 docker compose exec db psql -U postgres -d dify -c "\dt"docker compose ps里如果某个服务的状态是Up (healthy),那是最理想的;显示Up但没写健康状态,说明这个服务没有配 healthcheck,需要看日志。api日志的末尾如果出现类似 “Application startup complete” 或者 uvicorn 的监听地址信息,说明启动完成。
第三条命令很关键,它能验证数据库连接和迁移是否成功。如果这条命令能列出表结构,那说明db对外是通的,api连不上数据库的概率就很低了。反过来,如果api一直重启,而数据库能查,那问题大概率在.env里的账号密码或者DB_HOST上。
4. 起不来、卡住、反复重启:高频故障的排查链路
4.1 端口冲突的表现形式和正确改法
80 端口被占用的时候,典型现象是nginx或者web容器起不来,日志里是bind: address already in use。这时候有两步要做。
先找出是谁占着:
lsof -nP -iTCP:80 -sTCP:LISTEN这条命令会列出监听 80 端口的进程和 PID。Mac 上常见的是系统自带的 Apache(httpd)或者某些本地开发服务。确认之后,你有两个选择:干掉占用进程,或者给 Dify 换个端口。
我的建议是换端口,别去动系统自带的服务,因为你不知道它被什么依赖着。改.env:
EXPOSE_NGINX_PORT=8080然后docker compose down再docker compose up -d。注意down的时候不要带-v,带了会把数据卷一起删掉,这是新手最容易犯的致命错误之一。
改完之后有个细节:容器重启后 nginx 的端口映射才会生效,而浏览器缓存可能还记着旧的地址,记得用无痕窗口打开验证。
4.2 容器反复重启,日志该从哪一段看起
Restarting是一个很模糊的状态,它可能来自进程崩溃、健康检查失败、依赖服务没就绪。排查的顺序应该是“从被依赖的往依赖的查”:
- 先看
db和redis是不是稳定的Up。这两个是底座,它们不稳,上面全倒。 - 再看
api。它是业务核心,日志信息量最大。 - 最后看
worker和plugin_daemon,这两个通常是被api或redis牵连的。
看日志不要用docker compose logs一把梭,输出会非常长。用--tail和-f组合:
docker compose logs -f --tail=100 api如果日志刷得太快看不清,先看退出码:
docker compose ps -a --format "table {{.Name}}\t{{.State}}\t{{.ExitCode}}"常见的退出码对照:
| 退出码 | 含义 | 常见原因 |
|---|---|---|
| 0 | 正常退出 | 容器执行完任务结束了,Dify 里少见 |
| 1 | 应用错误 | 配置错误、连不上数据库、密钥不对 |
| 137 | 被 OOM 杀掉 | Docker 内存配额太小 |
| 139 | 段错误 | 镜像架构不匹配,amd64 镜像跑在 arm64 上 |
| 143 | 收到 SIGTERM | 被正常停止,通常是down操作留下的记录 |
137 和 139 这两个是最有辨识度的,看到就知道问题方向了。
4.3 镜像拉取卡住不动,怎么判断是真的卡了还是慢
docker compose up -d之后终端半天没反应,很多人以为卡死了。判断方法很简单,另开一个窗口跑:
docker stats --no-stream如果能看到容器在跑或者镜像在下载,docker system df里的镜像大小在增长,那就是慢,不是卡。反过来,如果半小时过去一点动静都没有,那才是真卡。
这种时候不要反复Ctrl+C重来,因为已经下载的层是有缓存的,中断再续不会丢。可以试着先单独拉某一个镜像,把问题隔离出来:
docker pull langgenius/dify-api:latest如果单个镜像能拉下来,说明问题不在单项网络,可能是并发数太高。可以在 Docker Desktop 的 Settings 里把并发下载数调低,或者在 compose 里给每个服务单独加pull_policy控制。
提示:拉镜像的阶段别关 Docker Desktop,也别让 Mac 进入睡眠。Docker Desktop 在系统睡眠时会把虚拟机挂起,下载中的连接会断掉,醒来之后部分层需要重新拉。
4.4plugin_daemon与sandbox:两个容易被误判的“故障”
新版本的两个容器经常让人误以为系统坏了。
sandbox是代码执行沙箱,负责跑工作流里的代码节点。它的日志在没有任何任务的时候是安静的,docker compose ps里显示Up就够,不需要它有输出。有人看到它是Up但日志空,以为挂了,其实完全正常。
plugin_daemon负责管理插件生命周期。它的典型行为是:启动后建立与api的连接,然后空闲。如果你在控制台里装了模型供应商插件,plugin_daemon会开始拉取插件包,这时候日志会活跃起来,并且偶尔因为插件安装失败出现短暂的Restarting。这种情况不需要处理,等它自己恢复就行;如果一直重启,那多半是插件包下载失败,去控制台的插件管理页面卸载重装即可。
真正需要处理的是api和worker同时不稳定,那通常意味着它们共享的redis或者db出了问题。
5. 接上本地模型:容器和宿主机之间那道网络墙
5.1host.docker.internal到底是什么,为什么必须用它
这套环境跑起来之后,最大的价值点是接入模型。你可以接在线 API,也可以接本机跑的模型。后者对 Mac 用户来说更有吸引力,因为不花钱、不联网、数据完全不出机器。
但这里有个结构性的问题:Dify 的api和worker跑在 Docker 容器里,而本地模型服务(比如用 Ollama 跑的那种)跑在 macOS 宿主机上,两者不在同一个网络命名空间。容器内写localhost:11434指向的是容器自己,不是你的 Mac。
Docker Desktop 为了这个场景专门提供了一个特殊域名:host.docker.internal,容器内访问它会解析到宿主机的地址。所以在 Dify 的模型供应商配置里,Ollama 的 Base URL 要填:
http://host.docker.internal:11434这个域名在 Linux 版 Docker 上默认不存在,需要额外配--add-host,但在 Mac 上是开箱即用的,这算是 Mac 用户的一点便利。
5.2 光改 Dify 那一端没用,Ollama 侧必须动的一个配置
我第一次接的时候,在 Dify 里填完地址测试连接,一直报连接超时。查了半天才发现问题不在 Dify,在 Ollama。
Ollama 默认只监听127.0.0.1:11434,这个地址只接受来自本机的连接,容器通过host.docker.internal过来的请求源地址不是 127.0.0.1,所以会被拒绝。要让它接受外部连接,必须设置监听地址:
launchctl setenv OLLAMA_HOST "0.0.0.0"设置完要重启 Ollama 应用(注意是从菜单栏完全退出再启动,不是关窗口)。验证方法很简单,在终端里跑:
curl http://127.0.0.1:11434/api/tags能返回模型列表就说明服务活着,再跑:
curl http://host.docker.internal:11434/api/tags这一条如果在宿主机上能通,容器里基本也能通。
还有个隐性问题:Dify 内置了一个ssrf_proxy容器,用途是防止服务端请求伪造攻击,它会拦截指向内网地址的请求。host.docker.internal解析出来的是内网地址,有可能被它挡掉。较新版本在.env里提供了放开内网访问的开关,具体变量名以你本地那份.env.example为准,找到和 SSRF、private IP 相关的注释项打开即可。如果找不到开关,也可以临时把api的入口改成直连——但这样等于放弃了防护,只建议在完全离线的个人机器上这么做。
5.3 模型接上之后,响应速度该怎么客观评估
接上模型之后,很多人的第一反应是“怎么这么慢”。这里要分开看两段耗时:模型推理本身的时间,和 Dify 平台处理的时间。
推理时间取决于你选的模型和 Mac 的芯片。在 M 系列芯片上,Ollama 会走 Metal 加速,7B 级别的模型在 16G 内存机器上能跑到比较流畅的程度,32B 以上就会明显吃力,因为模型权重要全部读进内存,内存不够就会开始用交换空间,速度断崖式下跌。
平台处理时间主要花在检索和上下文拼接上。如果你的应用挂了知识库,每轮对话都要做一次向量检索,这个开销和知识库大小、检索 topK 设置直接相关。判断方法:在 Dify 的日志页面看每步节点的耗时,如果时间大头在“知识检索”节点,那就调小 topK;如果大头在“LLM”节点,那就是模型本身慢,换小模型或者减少上下文长度。
还有一个容易忽略的点:第一次调用某个模型会明显慢,因为模型需要加载进内存。之后的调用会快很多。所以别用第一次的响应时间做判断。
6. 长期跑下去:数据备份、版本升级和资源回收
6.1 数据到底散落在哪几个地方
要备份,先要知道数据都在哪。Dify 本地部署的数据大致分布在四个位置:
| 位置 | 内容 | 丢失后果 |
|---|---|---|
docker/volumes/db/data | PostgreSQL 数据文件 | 应用、对话、配置全没了 |
docker/volumes/app/storage | 上传的原始文档、生成的图片 | 知识库变成空壳,文件引用全断 |
docker/volumes/weaviate | 向量库索引 | 检索功能失效,需要重建索引 |
docker/volumes/plugin_daemon | 已安装的插件 | 插件需要重新安装 |
最稳妥的备份方式是两层:一层是目录级快照,把整个docker/volumes打包;另一层是数据库逻辑备份,用pg_dump导出 SQL。后者在跨版本升级时更可靠,因为数据文件格式可能随版本变化。
# 逻辑备份,容器名以 docker compose ps 显示为准 docker compose exec -T db pg_dump -U postgres -d dify > ~/backup/dify_$(date +%Y%m%d).sql-T参数很关键,它禁用伪终端分配,不加的话在脚本里跑会报错。
6.2 升级的正确姿势,以及为什么不能偷懒
Dify 迭代速度很快,几个月就有一次比较大的更新。升级流程本身不复杂,但顺序错了会很麻烦:
cd dify git pull cd docker docker compose down docker compose pull docker compose up -d四个关键点:
第一,git pull之后一定要看一眼.env.example有没有新增变量。新版本经常加新配置项,你的旧.env里没有,缺了会导致某些功能异常但启动不报错,排查起来很痛苦。用diff .env .env.example对比一下最直接。
第二,docker compose down不加-v。加了就等于把整个volumes目录清空,数据库和文档全没。这是不可逆的,只能靠备份恢复。
第三,pull之前先把数据库备份出来。跨大版本升级时,api容器启动会执行数据库迁移,迁移有极小概率失败,失败之后旧版本可能也回不去了。
第四,升级完成后不要只看容器是否Up,一定要实际登录控制台点几下,发一条对话试试。有些问题只在业务路径上才暴露。
6.3 Mac 上长期挂着跑,内存和磁盘的回收习惯
Mac 不是服务器,长期挂着一套 Docker 环境会有两个副作用:内存被长期占用,磁盘缓慢增长。
内存方面,Docker Desktop 的虚拟机内存是整体分配的,即使容器空转也会占着。如果你不是天天用,建议养成习惯:用完docker compose stop(保留数据,只停容器),需要时再docker compose start。这样虚拟机里的进程停了,宿主机内存占用会明显下降。
磁盘方面,镜像更新会留下大量悬空镜像和构建缓存。每隔一段时间跑一下:
docker system df看Reclaimable那一列。清理的时候分两步,先清最安全的:
docker image prune -f这个命令只删除没有标签的悬空镜像。确认没问题之后再考虑docker builder prune,它会删构建缓存。docker system prune -a这个命令我不建议在跑着 Dify 的机器上随手敲,-a会把所有没在运行容器里使用的镜像都删掉,下次启动又得重新拉一遍。
还有一个小经验:docker/volumes/app/storage下面会积累你测试时上传的各种文件,包括一些你自己都忘了的 PDF 和表格。定期进去看看,把不需要的清掉,比等到磁盘告警了再处理要轻松得多。
最后分享一个我自己的习惯:在docker/volumes同级目录放一个README.md,写清楚这台机器上跑的是什么版本、端口是多少、管理员账号在哪记着、备份放在哪。三个月后你再回来动这套环境,一定会感谢当时的自己。