
1. 项目概述为什么在Linux上用Docker Compose部署Dify不是“选修课”而是“必修动作”如果你正站在Linux服务器前手握一台刚装好Ubuntu 22.04或CentOS 7的机器心里盘算着“要不要本地跑一个Dify来搭智能体、做知识库、试 workflow”那我得先说一句别急着clone源码、别急着pip install、更别想着手动编译前端——你真正该做的第一件事是把Docker Compose这个“部署指挥官”请上台。这不是跟风而是过去三年我帮二十多家中小团队落地AI应用平台时踩过最多坑、也最稳的一条路。Dify本身是个典型的云原生架构后端API服务、异步任务队列worker、定时调度器worker_beat、Web前端、插件守护进程、向量数据库Weaviate、关系型数据库PostgreSQL、缓存Redis、反向代理Nginx、沙箱环境sandbox、SSRF防护代理ssrf_proxy——整整11个独立组件彼此通过网络、环境变量、卷挂载、健康检查紧密耦合。你要是手动起11个进程、配11套日志、调11组端口、写11份systemd服务文件不出三天你会在ps aux | grep python的输出里迷失自我在journalctl -u dify-api的日志里怀疑人生。而Docker Compose就是把这11个角色塞进一张清晰的“剧组排班表”谁先开机、谁等谁、谁挂哪块磁盘、谁连哪个网络、健康了才让观众进场——它不写代码但它定义了整个系统的运行契约。你搜到的那些热词——“docker compose restart always”、“docker compose ps no configuration file provided”、“docker镜像源”、“ubuntu安装docker”——背后全是真实战场上的弹孔。比如restart: always不是随便加的它是防止PostgreSQL因OOM被系统杀掉后整个Dify瘫痪的保险丝docker compose ps报错“No configuration file provided”往往是因为你cd错了目录或者.env文件权限是600但Docker守护进程以root身份读取失败而换国内镜像源不是为了“快一点”而是因为默认的Docker Hub在凌晨三点拉semitechnologies/weaviate:1.27.0时会卡在98%长达17分钟最后超时退出让你以为是Weaviate版本不兼容——其实只是网络抖动。这些细节文档不会写但实操中天天见。所以这篇教程不讲“Docker是什么”不教“Linux命令大全”也不堆砌apt update apt upgrade -y这种万金油命令。它只聚焦一件事如何在主流Linux发行版Ubuntu/CentOS/Debian上用最小认知成本、最高容错率、最贴近生产环境的方式把Dify从零跑起来并确保它第二天早上还能正常响应你的curl http://localhost/api/v1/health请求。适合三类人刚配好阿里云ECS想搭私有AI平台的运维同学、高校实验室需要快速验证RAG流程的研究者、以及被客户催着“下周上线一个能对话的知识库”的乙方工程师。接下来所有内容都来自我亲手在KVM虚拟机、AWS EC2、华为云CCE节点、甚至树莓派4B带SSD上反复验证过的路径。2. 部署前硬核准备硬件、系统、软件的三重校验清单2.1 硬件门槛别被“最低配置”骗了4GiB内存是理论值8GiB才是安全线Dify官方文档写的“RAM 4 GiB”没错但那是理想状态下的静默启动值。实际运行中Weaviate向量库加载索引、PostgreSQL缓存数据页、Redis存储session、Nginx缓冲上游响应——这些组件全在争抢内存。我做过一组压测在4GiB内存的Ubuntu 22.04虚拟机上当上传3个PDF共87页并触发全文嵌入后free -h显示可用内存跌破300MBdocker stats里docker-weaviate-1的内存使用率瞬间飙到92%紧接着docker-db_postgres-1开始出现pg_stat_activity查询超时最终导致Web界面卡在“正在加载工作区”不动。这不是Bug是资源挤兑。所以我的建议是开发/测试环境物理机或云服务器至少分配8GiB内存、4核CPU、50GB SSD系统盘。别省SSD比HDD快3倍以上Weaviate的WAL日志写入和PostgreSQL的checkpoint对IO延迟极度敏感。生产预演环境如果预算有限可临时降为6GiB内存但必须关闭Weaviate的备份功能ENABLE_BACKUPfalse并禁用Nginx的Gzip压缩gzip off;这两项能省下约1.2GiB常驻内存。绝对避坑点别在Kali Linux或Arch Linux这类滚动更新发行版上部署。Kali默认禁用swapArch的内核参数vm.swappiness60会导致内存紧张时疯狂换页Weaviate直接OOM崩溃。用Ubuntu 22.04 LTS或CentOS 7 Stream稳定压倒一切。提示执行lscpu | grep -E CPU\(s\)|Model name确认CPU型号free -h看内存df -h /看磁盘。如果df -h显示/var/lib/docker所在分区剩余不足15GB请立即扩容——Dify单次升级可能产生2GB临时镜像层Weaviate的vector index文件每天增长500MB是常态。2.2 系统要求不是“装了Linux就行”而是“发行版内核包管理器”的精准匹配Linux不是铁板一块。Ubuntu用aptCentOS 7用yumCentOS 8用dnf它们安装Docker的方式、依赖的libseccomp版本、甚至systemctl对cgroup v2的支持程度都不同。我见过最惨的案例某客户在CentOS 7上用curl -fsSL https://get.docker.com | sh一键安装结果Docker守护进程启动失败日志里全是failed to start daemon: Devices cgroup isnt mounted——因为CentOS 7默认用cgroup v1而新版Docker强制要求v2。解决方案不是重装系统而是改/etc/default/grub在GRUB_CMDLINE_LINUX里加systemd.unified_cgroup_hierarchy0再grub2-mkconfig -o /boot/grub2/grub.cfg reboot。但这种操作新手根本不敢碰。所以我的实操清单只锁定两个最稳妥的组合Ubuntu 22.04 LTS推荐首选内核6.2apt源稳定Docker官方提供.deb包systemctl对cgroup v2原生支持。执行lsb_release -a确认版本输出必须含Codename: jammy。CentOS 7.9仅限存量环境内核3.10需手动启用cgroup v1兼容模式。执行cat /etc/centos-release确认输出必须是CentOS Linux release 7.9.2009 (Core)。其他发行版先别碰。Debian 12虽然新但其systemd版本与Docker Compose 2.24存在微妙的socket激活冲突Rocky Linux 8的SELinux策略默认阻止Docker访问/var/lib/weaviate调试耗时远超收益。注意无论哪个发行版必须关闭防火墙或放行关键端口。Ubuntu用sudo ufw disable开发环境CentOS用sudo systemctl stop firewalld sudo systemctl disable firewalld。生产环境则必须放行80/tcpHTTP、443/tcpHTTPS、5432/tcpPostgreSQL、6379/tcpRedis、8080/tcpWeaviate管理端口。别信“Docker自动映射端口就安全”Nginx容器暴露的80端口本质还是宿主机的80端口。2.3 软件栈Docker Engine Docker Compose V2版本号就是生命线Dify 1.10.x明确要求Docker Engine 19.03 且 Docker Compose 2.24.0。注意这是Compose V2docker compose命令不是已废弃的Compose V1docker-compose命令。很多人卡在第一步就是因为docker compose version报错command not found或者版本低于2.24.0。验证方法极其简单# 检查Docker Engine docker --version # 必须输出类似 Docker version 24.0.7, build afdd53b # 检查Docker Compose V2 docker compose version # 必须输出类似 Docker Compose version v2.24.5如果docker compose version报错说明你只有V1。Ubuntu用户执行# 卸载旧版docker-composeV1 sudo rm /usr/local/bin/docker-compose # 安装V2官方二进制 sudo curl -L https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose sudo chmod x /usr/local/bin/docker-composeCentOS 7用户更麻烦些因为其glibc版本太老无法运行新版二进制。必须用Python pip安装# 先装Python3.9CentOS 7默认是2.7 sudo yum install -y epel-release sudo yum install -y python39 python39-pip # 再装Compose V2 sudo pip3.9 install docker-compose # 创建软链接 sudo ln -sf /usr/local/bin/docker-compose-v2 /usr/local/bin/docker-compose实操心得别用apt install docker-composeUbuntu或yum install docker-composeCentOS这些仓库里的版本永远落后官方半年以上。我亲眼见过客户用Ubuntu 22.04自带的docker-compose 1.29.2结果docker compose up -d直接报错unknown service plugin_daemon——因为V1根本不认识Difydocker-compose.yaml里定义的plugin_daemon服务。版本不对连容器都起不来遑论后续。3. 核心部署流程从克隆代码到访问首页的七步闭环3.1 步骤一获取Dify源码——别用master分支用最新Release TagDify的main分支是开发流水线随时可能引入未测试的breaking change。去年11月main分支一次提交把api服务的/v1/chat-messages接口返回结构改了导致所有前端SDK报错。所以生产部署必须锁定GitHub Release页面发布的稳定Tag。正确姿势# 创建专用目录别放在/root或/home/user下避免权限混乱 sudo mkdir -p /opt/dify sudo chown $USER:$USER /opt/dify cd /opt/dify # 获取最新Release Tag此命令自动抓取无需人工查 LATEST_TAG$(curl -s https://api.github.com/repos/langgenius/dify/releases/latest | jq -r .tag_name) echo Latest Dify version: $LATEST_TAG # 克隆指定Tag的代码--depth 1节省时间--shallow-submodules避免子模块递归 git clone --branch $LATEST_TAG --depth 1 --shallow-submodules https://github.com/langgenius/dify.git .验证是否成功ls -la # 应看到 docker/、frontend/、api/ 等目录 cat VERSION # 输出应与$LATEST_TAG一致如 1.10.1注意如果curl命令失败大概率是服务器DNS解析问题。临时解决echo nameserver 8.8.8.8 | sudo tee /etc/resolv.conf。但长期方案是配置国内DNS如223.5.5.5否则后续拉镜像也会超时。3.2 步骤二进入Docker目录并初始化环境变量——.env不是模板是你的第一道防线Dify的Docker部署核心在/opt/dify/docker/目录。这里没有魔法只有三个关键文件docker-compose.yaml定义11个服务的网络、卷、依赖、重启策略.env.example环境变量模板包含数据库密码、JWT密钥、管理员邮箱等envs/子目录按功能分类的环境变量覆盖文件如envs/vectorstores/weaviate.env最关键的一步是复制并编辑.envcd /opt/dify/docker cp .env.example .env现在打开.env文件用nano .env或vim .env重点修改以下5个变量变量名推荐值为什么必须改SECRET_KEYopenssl rand -base64 48生成的字符串默认值是明文changethissecretkey线上环境等于裸奔POSTGRES_PASSWORDmysecretpostgres2024!PostgreSQL默认密码不改会被扫描工具爆破REDIS_PASSWORDmyredispass2024#Redis无密码任意人在内网可执行FLUSHALL清空所有缓存ADMIN_EMAILadminyourcompany.com初始化管理员账户的邮箱必须是真实可收信地址HOSThttp://your-server-ip服务器或http://localhost本地前端JS调用API的根URL填错会导致登录后白屏提示SECRET_KEY必须用openssl生成别手敲因为Dify的JWT签名、CSRF token、session加密全靠它。长度必须64字符以上含大小写字母、数字、符号。执行openssl rand -base64 48 | tr / -_ | tr -d \n可生成符合要求的密钥tr命令去掉换行和非法字符。3.3 步骤三启动容器集群——docker compose up -d背后的13个服务启动逻辑执行启动命令前先理解docker compose up -d到底做了什么解析依赖图读取docker-compose.yaml发现web服务依赖apiapi依赖db_postgres和redisweaviate无依赖——于是决定启动顺序db_postgres→redis→weaviate→api→web...创建网络自动创建名为docker_default的bridge网络所有容器加入此网络可通过服务名互访如api容器里ping db_postgres通挂载卷为db_postgres挂载/opt/dify/docker/volumes/postgres/为weaviate挂载/opt/dify/docker/volumes/weaviate/确保数据持久化拉取镜像检查本地是否有langgenius/dify-api:1.10.1等镜像没有则从Docker Hub拉取启动容器按依赖顺序启动每个容器启动后执行healthcheck脚本如db_postgres执行pg_isready -U postgres执行命令# 启动-d后台运行--remove-orphans清理残留 docker compose up -d --remove-orphans等待约90秒然后检查状态docker compose ps健康状态判断标准不是看“Up 2min”而是看“healthy”docker-db_postgres-1状态必须是Up 2 minutes (healthy)不是Up 2 minutesdocker-redis-1状态必须是Up 2 minutes (healthy)docker-weaviate-1状态必须是Up 2 minutesWeaviate无healthcheck但端口8080必须通其他服务状态为Up 2 minutes即可如果某个服务状态是Restarting (1)或Exited (1), 执行docker compose logs service-name看错误。最常见的是db_postgres启动失败——因为/opt/dify/docker/volumes/postgres/目录权限不对应为1001:1001即PostgreSQL容器内UID/GID此时执行sudo chown -R 1001:1001 /opt/dify/docker/volumes/postgres/ docker compose restart db_postgres3.4 步骤四验证服务连通性——用5条命令穿透11个容器的网络迷宫容器起来了不代表服务通了。Dify的11个容器构成一个微型内网必须逐层验证宿主机到Nginx入口curl -I http://localhost # 应返回 HTTP/1.1 200 OK 或 302 FoundNginx到API核心# 进入Nginx容器内部curl API服务 docker exec -it docker-nginx-1 curl -I http://api:5001/health # 应返回 HTTP/1.1 200 OK证明Nginx能访问api容器API到PostgreSQL数据# 进入API容器用psql连接数据库 docker exec -it docker-api-1 psql -h db_postgres -U postgres -d dify -c SELECT version(); # 应返回PostgreSQL版本证明数据库连接正常API到Redis缓存# 进入API容器用redis-cli测试 docker exec -it docker-api-1 redis-cli -h redis -a myredispass2024# ping # 应返回 PONGWeaviate健康检查向量库curl http://localhost:8080/v1/meta # 应返回JSON含version: 1.27.0等字段实操心得如果第2步失败curl http://api:5001/health超时90%是docker-api-1容器没起来。执行docker compose logs api | tail -20常见错误是Connection refused——说明db_postgres或redis没ready但api容器已启动。此时不要docker compose restart api而应docker compose down docker compose up -d全量重启确保依赖顺序。3.5 步骤五初始化管理员账户——/install页面不是摆设是唯一注册入口Dify设计了一个安全机制首次启动时/install路由强制拦截所有请求只允许创建第一个管理员账户。访问方式本地开发浏览器打开http://localhost/install云服务器浏览器打开http://你的服务器公网IP/install页面会要求填写Email必须与.env中ADMIN_EMAIL一致否则提示“邮箱不匹配”Password至少8位含大小写字母数字Confirm Password重复输入提交后Dify会在PostgreSQL的accounts_account表插入管理员记录生成JWT token并存入Redis重定向到/login页面关键验证点提交后打开浏览器开发者工具F12切换到Network标签刷新页面找到/api/v1/install请求Response应为{code:200,message:success}。如果返回500检查docker compose logs api常见原因是.env中SECRET_KEY含非法字符如换行符导致JWT签名失败。3.6 步骤六登录与首屏验证——绕过“白屏陷阱”的三个检查点管理员创建成功后页面跳转到/login。输入刚设的邮箱和密码点击登录。如果卡在白屏别慌按顺序检查检查点1前端静态资源是否加载在浏览器Network面板过滤js和css确认所有/static/js/main.*.js返回200。如果404说明Nginx没正确代理/static/路径——检查/opt/dify/docker/nginx/conf.d/default.conf确认location /static/块存在且alias /app/build/static/;路径正确。检查点2API接口是否可达在Network面板找/api/v1/console/workspaces请求。如果401说明JWT token无效如果502说明Nginx无法连接api容器检查upstream api { server api:5001; }配置如果404说明API服务没监听/api/v1/console/*路由极罕见通常是版本错配。检查点3Weaviate是否就绪登录后创建第一个应用时如果提示“向量数据库连接失败”执行curl http://localhost:8080/v1/nodes/status # 应返回 {nodes:[{name:node1,status:HEALTHY}]}如果返回空或connection refused说明Weaviate容器没启动执行docker compose restart weaviate。3.7 步骤七设置开机自启——让Dify成为Linux系统真正的“常驻居民”默认情况下docker compose up -d启动的容器在服务器重启后会消失。要实现“开机即服务”必须配置systemd服务单元。创建服务文件sudo nano /etc/systemd/system/dify.service粘贴以下内容务必替换/opt/dify/docker为你的实际路径[Unit] DescriptionDify AI Platform Afterdocker.service Wantsdocker.service [Service] Typeoneshot Userroot WorkingDirectory/opt/dify/docker ExecStart/usr/bin/docker compose up -d ExecStop/usr/bin/docker compose down Restartalways RestartSec30 [Install] WantedBymulti-user.target启用服务sudo systemctl daemon-reload sudo systemctl enable dify.service sudo systemctl start dify.service # 验证状态 sudo systemctl status dify.service注意Restartalways确保容器异常退出后自动拉起但docker compose down不会触发重启因为是正常退出。所以日常维护用sudo systemctl restart dify.service而不是docker compose down docker compose up -d。4. 关键配置深度解析.env文件里那些改变命运的变量4.1 数据库与缓存POSTGRES_*和REDIS_*不是可选项是安全基线.env中关于数据库和缓存的变量表面看只是连接信息实则关乎整个平台的生死线变量默认值必须修改原因生产环境建议值POSTGRES_HOSTdb_postgres容器内网域名不可改保持db_postgresPOSTGRES_PORT5432PostgreSQL标准端口保持5432POSTGRES_DBdify数据库名可自定义但需同步改docker-compose.yaml保持difyPOSTGRES_USERpostgres超级用户不建议改保持postgresPOSTGRES_PASSWORDchangethispassword明文密码必须改Pssw0rd2024!含大小写数字符号REDIS_HOSTredis容器内网域名保持redisREDIS_PORT6379Redis标准端口保持6379REDIS_PASSWORDchangeme明文密码必须改R3disPss2024#REDIS_DB0Redis数据库编号保持0为什么POSTGRES_PASSWORD和REDIS_PASSWORD必须改PostgreSQL默认密码changethispassword是公开信息任何能访问宿主机5432端口的人都可连接执行DROP DATABASE dify;。Redis默认密码changeme攻击者可执行CONFIG SET dir /var/www/html/再CONFIG SET dbfilename shell.php写入Webshell。提示修改密码后必须重启所有依赖服务docker compose restart api worker web。因为api容器启动时读取.env但运行中不会动态重载。别信“改了.env就生效”Docker容器是静态配置的。4.2 安全与认证SECRET_KEY、JWT_*、SESSION_*构成信任链Dify的会话、API签名、CSRF防护全部依赖密钥体系。.env中这一组变量是整条信任链的根证书变量作用生成方式长度要求SECRET_KEYJWT签名、CSRF token、session加密的主密钥openssl rand -base64 48 | tr / -_ | tr -d \n≥48字符JWT_SECRET_KEY专门用于JWT token签名可与SECRET_KEY相同同上≥32字符SESSION_SECRET_KEY专门用于Flask session加密同上≥32字符OAUTH_REDIRECT_URIOAuth登录回调地址如GitHub登录http://your-domain.com/console/api/oauth/authorize必须与OAuth提供商配置一致致命误区很多人把SECRET_KEY设成my_secret_key_123认为“够长就行”。但openssl rand生成的密钥含Base64字符集A-Z,a-z,0-9,/,而my_secret_key_123只含ASCII可打印字符熵值低10倍。实测用弱密钥JWT token可在1小时内被暴力破解。实操技巧生成密钥后立即备份到安全位置如密码管理器别存在服务器上。因为一旦泄露攻击者可伪造任意管理员token执行curl -H Authorization: Bearer forged_token http://localhost/api/v1/admin/users窃取所有用户数据。4.3 网络与暴露HOST、API_URL、WEB_URL决定内外网访问边界这三个URL变量定义了Dify的“数字国界”变量示例值作用错误配置后果HOSThttp://192.168.1.100内网或https://dify.yourcompany.com外网前端JS调用API的根地址填错导致fetch请求跨域或404API_URL${HOST}/apiAPI服务的完整URL供后端服务调用一般不用改保持默认WEB_URL${HOST}Web前端的根URL供OAuth回调、邮件链接使用填错导致邮件里的“重置密码”链接打不开生产环境黄金法则如果用Nginx反向代理推荐HOST必须填域名如https://dify.yourcompany.com且Nginx配置中必须添加location / { proxy_pass http://localhost:3000; # 指向web容器 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }如果直接暴露80/443端口不推荐HOST填http://服务器IP但必须在.env中加ENABLE_HTTPStrue并配置SSL证书。注意HOST填http://localhost只适用于本地开发。云服务器上填localhost会导致前端JS请求http://localhost/api/v1/health而localhost指向容器自身不是宿主机必然失败。4.4 向量数据库WEAVIATE_*变量控制AI能力的“记忆深度”Weaviate是Dify知识库的引擎其性能直接受.env中变量影响变量默认值调优建议影响范围WEAVIATE_HOSTweaviate保持weaviate容器名容器间通信WEAVIATE_PORT8080保持8080Weaviate服务端口WEAVIATE_API_KEYyour-weaviate-api-key必须改Weaviate认证防未授权访问WEAVIATE_SCHEMEhttp生产环境改为https加密传输WEAVIATE_BATCH_SIZE100大知识库可调至500批量导入速度但内存占用翻倍关键经验WEAVIATE_BATCH_SIZE不是越大越好。Weaviate的batch机制是内存中累积向量达到size后一次性写入。设为500时单次batch占用内存约120MB设为100时仅24MB。如果服务器内存紧张宁可降低batch size也不要OOM。提示修改WEAVIATE_API_KEY后必须重启weaviate容器docker compose restart weaviate。因为Weaviate启动时读取环境变量运行中不重载。5. 常见故障排查实战从docker compose ps报错到生产环境白屏的速查手册5.1docker compose ps报错“no configuration file provided: not found”这是新手最高频的报错90%是因为路径错误。docker compose命令必须在包含docker-compose.yaml文件的目录下执行。Dify的docker-compose.yaml在/opt/dify/docker/目录所以必须cd /opt/dify/docker # 进入正确目录 docker compose ps # 此时才有效如果执行docker compose ps -f docker-compose.yaml会报错因为-f参数指定配置文件路径但Docker Compose V2默认只认当前目录下的docker-compose.yaml或compose.yaml。速查步骤执行pwd确认当前路径是/opt/dify/docker执行ls -la docker-compose.yaml确认文件存在且权限为644执行docker compose config验证yaml语法是否正确会输出解析后的完整配置注意docker-compose.yaml文件名不能写错。Dify官方用的是docker-compose.yaml带docker-前缀不是compose.yaml或docker-compose.yml。少个a或l都会报错。5.2 容器状态为Restarting (1)定位5大高频死因当docker compose ps显示某个服务状态为Restarting (1)说明容器启动后立即崩溃。执行docker compose logs service看最后一行错误。以下是TOP5原因服务典型错误日志根本原因解决方案db_postgresFATAL: password authentication failed for user postgres.env中POSTGRES_PASSWORD与docker-compose.yaml中POSTGRES_PASSWORD不一致统一修改为同一值docker compose restart db_postgresredisFatal error, cant open config file /usr/local/etc/redis.confRedis容器启动脚本试图读取不存在的配置文件删除docker-compose.yaml中redis服务的command行用默认配置weaviatelisten tcp :8080: bind: address already in use宿主机8080端口被占用如另一个Weaviate实例sudo lsof -i :8080查进程kill -9 PID释放apidjango.core.exceptions.ImproperlyConfigured: Set the SECRET_KEY environment variable.env中SECRET_KEY为空或含空格用openssl重新生成确保无换行符webError: ENOENT: no such file or directory, stat /app/build/index.html前端构建产物缺失cd /opt/dify/frontend