阿里云Ubuntu部署Dify全指南:从零构建AI应用平台

1. 项目概述:为什么在阿里云Ubuntu上从零部署Dify是当前最务实的选择

最近三个月,我帮超过23位客户和同行朋友在阿里云ECS上部署Dify,其中17位用的是Ubuntu系统(主要是22.04 LTS),剩下6位用CentOS或Rocky Linux。几乎所有人问的第一个问题都是:“能不能直接用宝塔面板点几下就装好?”——答案很明确:不能,而且强烈不建议。Dify不是WordPress那种传统Web应用,它由至少7个独立容器协同工作(nginx、web、api、worker、celery-beat、redis、postgresql),每个容器之间通过内部网络通信,还依赖一套严格校验的环境变量体系。宝塔的“一键部署”模块对这类多服务AI平台的支持极其有限,强行套用会导致数据库连接失败、知识库索引中断、工作流任务卡死等隐蔽性极强的问题,排查起来比重装还费时间。

我之所以坚持“从零开始”,是因为Dify官方docker-compose方案本身已经足够成熟稳定,真正卡住新手的从来不是技术门槛,而是三个被绝大多数教程忽略的细节:端口映射的语义陷阱、.env文件生成的不可跳过性、以及阿里云安全组与本地防火墙的双重放行逻辑。比如,很多教程写“修改docker-compose.yaml里的80端口为8080”,但没说清楚:你改的是ports字段(容器对外暴露的端口),而不是NGINX_PORT环境变量(容器内部Nginx监听的端口)。后者一旦被误改,Nginx进程会因配置语法错误而无限重启,日志里只显示invalid number of arguments in "worker_processes" directive这种毫无指向性的报错,新手根本无从下手。

更关键的是,Ubuntu系统在阿里云镜像市场里默认不预装Docker。网上流传的“阿里云服务器Docker是自带的”说法,混淆了阿里云容器服务(ACK)和普通ECS实例的概念。ACK是Kubernetes托管服务,而我们部署Dify用的是ECS上的Docker Engine,必须手动安装。实测下来,Ubuntu 22.04在阿里云ECS上安装Docker最稳的方式,是直接使用Docker官方的一键脚本配合阿里云镜像源,而不是用apt install docker.io——后者安装的是社区维护的旧版本,与Dify官方镜像的glibc版本存在兼容风险,曾导致我在一台4核8G的ECS上遇到API服务启动后立即OOM Killed的问题。

所以这篇内容不是教你怎么“复制粘贴命令”,而是带你理清每一个操作背后的必然性。你会明白为什么cp .env.example .env这一步必须放在修改端口之前,为什么docker compose up -d之后要立刻用docker ps | grep nginx验证,而不是直接打开浏览器。这些动作背后,是Dify容器化架构的设计哲学:配置即代码,环境即契约。当你理解了这个底层逻辑,以后部署任何基于Docker Compose的AI平台(比如Ollama+Qwen3.5:9b组合、或者LlamaIndex工作流),都能举一反三。

2. 环境准备与基础工具链搭建:Ubuntu系统上的精准手术刀式初始化

2.1 阿里云ECS实例选型与系统初始化

在阿里云控制台创建ECS实例时,很多人会下意识选择“最新版Ubuntu镜像”。但这里有个隐藏坑:阿里云官方提供的Ubuntu镜像(如ubuntu_22_04_x64_20G_alibase_20231212.vhd)虽然系统干净,但默认启用了systemd-resolved作为DNS解析器,而Dify的PostgreSQL容器在初始化时会尝试解析host.docker.internal这个特殊域名,Ubuntu的systemd-resolved对此支持不完善,容易导致数据库连接超时。我的解决方案是:创建实例时,在“自定义数据”字段中粘贴以下cloud-init脚本

#cloud-config runcmd: - systemctl stop systemd-resolved - systemctl disable systemd-resolved - echo "nameserver 223.5.5.5" > /etc/resolv.conf - echo "nameserver 114.114.114.114" >> /etc/resolv.conf

这段脚本会在实例首次启动时自动执行,强制停用systemd-resolved并写入阿里云公共DNS(223.5.5.5是阿里云DNS主地址,响应速度比8.8.8.8快30%以上)。实测对比显示,启用该脚本后,Dify首次启动时PostgreSQL初始化耗时从平均92秒降至27秒,且100%成功,避免了因DNS解析失败导致的database is uninitialized and superuser password is not specified这类报错。

实例规格方面,Dify的最低运行要求是2核4G内存,但这是理论值。实际部署中,如果你计划接入知识库(尤其是PDF/Word文档解析)或运行工作流中的LLM调用,建议直接选择4核8G起步。原因在于:Dify的worker容器在处理文档切片时会启动多个Python子进程,每个进程默认占用约300MB内存;而api容器在高并发请求下,Gunicorn worker进程会动态扩容。我曾在一个2核4G实例上测试,当同时上传3份50页PDF并触发向量嵌入时,系统内存占用瞬间冲到98%,swap分区被频繁读写,最终导致Redis连接断开,整个平台进入半瘫痪状态。升级到4核8G后,同样负载下内存占用稳定在65%左右,响应延迟降低40%。

2.2 Docker与Docker Compose的精准安装:绕过Ubuntu仓库的版本陷阱

Ubuntu官方仓库中的docker.io包,版本长期停留在20.10.x,而Dify官方文档明确要求Docker Engine 24.0.0+。更重要的是,旧版Docker对cgroup v2的支持不完整,而Ubuntu 22.04默认启用cgroup v2,这会导致Dify的celery-beat容器在定时任务调度时出现Failed to set cgroup for process错误。因此,必须绕过apt,采用Docker官方推荐的安装方式:

# 1. 卸载可能存在的旧版本(如果之前装过) sudo apt-get remove docker docker-engine docker.io containerd runc # 2. 安装必要依赖 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg lsb-release # 3. 添加Docker官方GPG密钥(使用阿里云镜像加速下载) curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 4. 添加Docker仓库(关键:指定为阿里云镜像源) echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 5. 安装Docker Engine(指定最新稳定版,避免自动升级到测试版) sudo apt-get update sudo apt-get install -y docker-ce=5:24.0.7-1~ubuntu.22.04~jammy docker-ce-cli=5:24.0.7-1~ubuntu.22.04~jammy containerd.io # 6. 安装Docker Compose(注意:不是docker-compose,而是v2版本) sudo apt-get install -y docker-compose-plugin

这里有几个必须强调的细节:第一,docker-cedocker-ce-cli的版本号必须严格匹配(如5:24.0.7-1~ubuntu.22.04~jammy),否则会出现docker: 'compose' is not a docker command的报错;第二,docker-compose-plugin是Docker官方推荐的v2插件,它与docker compose命令完全兼容,但比旧版docker-compose二进制文件更稳定;第三,所有URL都指向mirrors.aliyun.com,这是阿里云官方镜像站,下载速度比GitHub Release快5-8倍,实测20MB的Docker CE包下载时间从3分12秒缩短至28秒。

安装完成后,务必验证Docker是否以非root用户运行:

# 将当前用户加入docker组(避免每次都要sudo) sudo usermod -aG docker $USER # 重新加载用户组(无需重启,比logout/login更快) newgrp docker # 验证安装 docker version | grep "Version:" docker compose version

如果docker version输出中Server Version显示为24.0.7,且docker compose version返回Docker Compose version v2.23.0,说明安装成功。此时可以运行docker run hello-world,看到Hello from Docker!即代表引擎正常。

2.3 阿里云安全组与系统防火墙的双重校准

很多新手部署失败,90%的原因出在端口放行环节。他们只记得在阿里云控制台开放8080端口,却忽略了Ubuntu系统自带的ufw防火墙。Ubuntu 22.04默认禁用ufw,但如果你之前手动启用过,或者使用了某些安全加固脚本,ufw可能处于活动状态,它会拦截所有未明确允许的入站连接,导致即使安全组开了8080,浏览器依然无法访问。

因此,在部署前必须做三重检查:

  1. 阿里云安全组检查:登录ECS控制台 → 找到目标实例 → 点击“安全组” → “配置规则” → “入方向” → 确认存在一条规则:类型为HTTP(8080),协议为TCP,端口范围为8080/8080,授权对象为0.0.0.0/0(或你自己的IP段)。注意:不要勾选“IPv6”,Dify目前不支持IPv6双栈。

  2. 系统防火墙检查

    # 查看ufw状态 sudo ufw status verbose # 如果显示“Status: active”,则必须放行8080端口 sudo ufw allow 8080 # 如果显示“Status: inactive”,则跳过此步
  3. 本地网络检查:如果你在公司内网或校园网,确认出口防火墙没有屏蔽8080端口。一个快速验证方法是:在ECS实例上运行python3 -m http.server 8080,然后从本地浏览器访问http://你的ECS公网IP:8080,如果能看到目录列表,说明网络链路畅通;如果打不开,则问题出在本地网络策略,需联系IT部门。

提示:Dify官方默认使用HTTP协议,不要尝试在部署初期就配置HTTPS。Nginx的SSL证书配置涉及域名解析、Let's Encrypt挑战、证书自动续期等多个环节,属于进阶操作。先确保HTTP能稳定访问,再考虑反向代理加SSL,这是避免首战失利的关键原则。

3. Dify核心部署流程:从代码拉取到服务启动的每一步推演

3.1 源码拉取与目录结构认知:为什么必须进入docker子目录

Dify的GitHub仓库结构是典型的“单体仓库多部署模式”:根目录下有webapiworker等服务代码,而docker目录则存放了完整的容器化部署配置。很多新手会犯一个致命错误:在仓库根目录下执行docker compose up -d。这会导致Docker Compose找不到docker-compose.yaml文件(它其实在docker/子目录里),报错no such file or directory: docker-compose.yaml

正确的路径是:

# 1. 创建一个专用部署目录(避免污染家目录) mkdir -p ~/dify-deploy && cd ~/dify-deploy # 2. 克隆官方仓库(注意:只克隆master分支,不带历史记录,节省时间) git clone --depth 1 https://github.com/langgenius/dify.git # 3. 进入docker部署子目录(这是唯一有效的入口) cd dify/docker

进入dify/docker目录后,你会看到几个关键文件:

  • docker-compose.yaml:定义了7个服务的容器配置、网络、卷挂载等;
  • .env.example:环境变量模板,包含数据库密码、JWT密钥、Redis地址等敏感参数;
  • nginx/目录:存放Nginx配置文件,其中default.conf定义了反向代理规则;
  • scripts/目录:包含数据库初始化、迁移等辅助脚本。

这里需要特别说明.env.example的设计意图。Dify将所有可配置项集中在此文件中,而不是分散在各个服务的配置里。这是因为Dify采用“环境驱动”的架构:同一个镜像,通过注入不同的环境变量,就能适配开发、测试、生产等不同环境。.env.example中的每一行,都对应着Docker Compose中某个服务的environment字段。例如,.env.example里有一行POSTGRES_PASSWORD=dify,那么在docker-compose.yamlpostgresql服务块中,就会有POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}这样的引用。这种设计保证了配置的一致性和可追溯性,但也意味着:你绝不能跳过cp .env.example .env这一步,因为Docker Compose在启动时,会优先读取同目录下的.env文件来替换${VAR}占位符;如果没有.env文件,所有占位符都会变成空字符串,导致PostgreSQL启动时密码为空,进而引发认证失败。

3.2 .env文件生成与关键参数解读:那些不能改、必须改、建议改的变量

执行cp .env.example .env后,必须立即编辑.env文件。这不是可选项,而是强制步骤。我建议用nano编辑器(Ubuntu默认安装,比vi更友好):

nano .env

打开后,你会看到大约50行配置。下面我逐类说明哪些必须改、哪些不能动、哪些建议按需调整:

必须修改的3个核心参数(安全底线):

  • SECRET_KEY:这是Dify的JWT签名密钥,用于生成和验证管理员登录Token。.env.example中给的是changethissecretkey,必须替换成至少32位的随机字符串。生成方法:openssl rand -base64 32,然后复制结果粘贴覆盖。如果沿用默认值,任何知道这个密钥的人都能伪造管理员Token,这是严重的安全漏洞。
  • POSTGRES_PASSWORD:PostgreSQL数据库的root密码。默认是dify,建议改成复杂密码(如DifyDB@2024!Secure)。注意:这个密码必须与docker-compose.yamlpostgresql服务的POSTGRES_PASSWORD环境变量一致,而Dify官方配置正是通过${POSTGRES_PASSWORD}引用的,所以只要改.env文件即可,无需碰yaml。
  • REDIS_PASSWORD:Redis的密码。默认为空,但生产环境必须设置。生成方法同上,openssl rand -base64 16。Redis存储了Dify的会话、缓存和异步任务队列,密码为空等于把钥匙扔在门口。

严禁修改的5个参数(稳定性保障):

  • NGINX_PORTNGINX_WORKER_PROCESSESNGINX_WORKER_CONNECTIONSNGINX_KEEPALIVE_TIMEOUTNGINX_CLIENT_MAX_BODY_SIZE:这些是Nginx容器内部的配置参数,由nginx/Dockerfilenginx/default.conf共同控制。Dify官方已针对AI平台负载做了深度调优,比如NGINX_CLIENT_MAX_BODY_SIZE设为512m,是为了支持大文件知识库上传。如果你手动修改NGINX_PORT,会导致Nginx配置文件中的listen ${NGINX_PORT}指令失效,因为default.conf里写的是listen 80,硬编码了端口,与环境变量无关。这就是为什么前面强调:只能改ports字段,不能改NGINX_PORT

建议按需修改的参数(体验优化):

  • APP_ENV=production:保持默认即可。development模式会开启调试信息,暴露内部路径,不适合公网部署。
  • LOG_LEVEL=INFO:如果部署后遇到问题,可临时改为DEBUG,然后用docker logs docker-api-1查看详细日志。
  • CELERY_BROKER_URLCELERY_RESULT_BACKEND:这两个指向Redis,官方配置是redis://:${REDIS_PASSWORD}@redis:6379/0,其中redis是Docker内部服务名,6379是Redis容器的默认端口。除非你把Redis换成了外部集群,否则不要改。

编辑完成后,按Ctrl+O保存,Ctrl+X退出。此时.env文件已具备启动全部服务的最小必要配置。

3.3 docker-compose.yaml端口映射的精准手术:8080:80的深层含义

现在到了最关键的一步:修改docker-compose.yaml中的端口映射。打开文件:

nano docker-compose.yaml

找到nginx服务块(大概在第120行左右),定位到ports字段:

nginx: image: nginx:latest ports: - "${EXPOSE_NGINX_PORT:-80}:${NGINX_PORT:-80}"

这里有两个占位符:${EXPOSE_NGINX_PORT:-80}表示“如果环境变量EXPOSE_NGINX_PORT存在则用它的值,否则用80”;${NGINX_PORT:-80}同理。.env文件中并没有定义EXPOSE_NGINX_PORT,所以第一个占位符会回退到80;而NGINX_PORT.env中默认是80,所以整行等价于- "80:80"

但问题来了:阿里云ECS的80端口默认被系统级进程占用(如systemdsocket监听),或者被其他用户安装的Web服务(如Apache)抢占。docker compose up时会报Bind for 0.0.0.0:80 failed: port is already allocated。解决方案不是去杀掉占用进程(那可能影响系统稳定性),而是改变对外暴露的端口

ports字段修改为:

ports: - "8080:80"

注意:这里写死为8080:80,而不是"${EXPOSE_NGINX_PORT:-8080}:80"。为什么?因为EXPOSE_NGINX_PORT这个变量在.env中不存在,Docker Compose会尝试解析它,但找不到定义,就会报错The EXPOSE_NGINX_PORT variable is not set。而直接写死8080:80,语义清晰:把宿主机的8080端口,映射到容器内部的80端口。容器内的Nginx依然监听80端口(这是它的工作方式),只是外面的人通过8080来访问它。

这个映射关系可以用一个生活化类比理解:8080是快递柜的取件码(对外可见),80是柜子里某个格子的编号(对内有效)。你告诉快递员“把包裹放到8080号柜”,他输入密码后,系统自动把包裹放进80号格子。你不需要知道格子编号,只需要记住取件码。

修改完成后保存退出。此时,整个部署配置已满足启动条件:环境变量就绪、端口冲突规避、安全组待放行。

3.4 一键启动与状态验证:如何读懂docker ps的每一列信息

执行启动命令:

docker compose up -d

这个命令会后台启动所有服务。但真正的考验才刚刚开始——你需要学会“阅读”Docker的状态输出。等待约30秒后,运行:

docker ps

你会看到类似这样的输出(为便于说明,我添加了列标题):

CONTAINER IDIMAGECOMMANDCREATEDSTATUSPORTSNAMES
d93a23bcf8nginx:latest"sh -c 'cp /docker-e…"25 seconds agoUp 13 seconds0.0.0.0:443->443/tcp, :::443->443/tcp,0.0.0.0:8080->80/tcp, :::8080->80/tcpdocker-nginx-1
a1b2c3d4e5langgenius/dify:main"gunicorn --bind 0.0…"26 seconds agoUp 14 seconds (healthy)docker-api-1
f6g7h8i9j0postgres:15"docker-entrypoint.s…"27 seconds agoUp 15 seconds (healthy)docker-postgresql-1
k1l2m3n4o5redis:7-alpine"docker-entrypoint.s…"28 seconds agoUp 16 secondsdocker-redis-1

重点看三列:

  • STATUS列:必须显示Up X seconds,且后面没有(unhealthy)字样。如果显示Restarting (1) 2 seconds ago,说明容器启动失败,正在循环重启。
  • PORTS列:必须看到0.0.0.0:8080->80/tcp,这是端口映射成功的铁证。如果这里显示0.0.0.0:80->80/tcp,说明你改错了ports字段,需要重新编辑docker-compose.yaml
  • NAMES列:所有容器名都以docker-开头,这是Docker Compose自动添加的前缀,表明它们属于同一个项目(project name默认为目录名dify)。

如果一切正常,再验证各服务的健康状态:

# 查看nginx容器日志(确认配置加载无误) docker logs docker-nginx-1 2>&1 | tail -n 5 # 查看api容器日志(确认数据库连接成功) docker logs docker-api-1 2>&1 | grep "Application startup complete" # 查看postgresql容器日志(确认初始化完成) docker logs docker-postgresql-1 2>&1 | grep "database system is ready to accept connections"

如果docker logs docker-api-1中出现了Application startup complete,并且docker logs docker-postgresql-1中出现了database system is ready to accept connections,说明整个数据链路已经打通,Dify的核心服务已就绪。

4. 访问与初始化:从空白页面到管理员后台的完整路径

4.1 浏览器访问的精确URL与常见误区

docker ps确认所有容器状态为Up后,在浏览器中输入:

http://你的ECS公网IP:8080

注意:必须是http,不是https;端口号必须是8080,不是80;URL末尾不能加斜杠/),因为Dify的前端路由是SPA(单页应用),加斜杠可能导致404。

如果页面加载出来是空白,或者显示502 Bad Gateway,请立即执行以下三步诊断:

  1. 检查Nginx是否真的在转发

    # 进入nginx容器内部,查看反向代理配置是否生效 docker exec -it docker-nginx-1 cat /etc/nginx/conf.d/default.conf | grep "proxy_pass"

    正常输出应为:

    proxy_pass http://web:80; proxy_pass http://api:5001;

    这表示Nginx正把/路径的请求转发给web容器(前端),把/api路径的请求转发给api容器(后端)。如果这里显示的是proxy_pass http://localhost:80;,说明nginx/default.conf文件被意外修改过,需要恢复。

  2. 检查web容器是否健康

    docker ps | grep web

    如果STATUS列显示Up但没有(healthy),说明前端服务虽然启动了,但健康检查失败。此时看日志:

    docker logs docker-web-1 | tail -n 20

    常见原因是VUE_APP_API_BASE_URL环境变量配置错误。这个变量在.env文件中定义,默认是/api,表示前端调用API的根路径。如果被误改成http://localhost:5001/api,web容器会尝试跨域请求,被浏览器拦截。

  3. 检查DNS解析(针对域名访问场景):如果你已经绑定了域名(如dify.yourdomain.com),但用IP能访问、用域名不能访问,问题一定出在DNS解析。在本地电脑执行:

    nslookup dify.yourdomain.com

    如果返回的IP不是你的ECS公网IP,说明DNS记录未生效或配置错误。阿里云DNS解析通常需要5-10分钟生效,耐心等待。

4.2 管理员账号创建与初始配置:避开邮箱验证的“伪障碍”

首次访问页面,会跳转到注册页。这里有一个极易被忽略的细节:Dify默认关闭邮箱验证功能.env文件中有一行SMTP_SERVER=,它是空的,这意味着SMTP服务未配置,系统不会发送验证邮件。所以你填写邮箱后,点击“注册”,页面会直接跳转到登录页,而不是提示“请查收邮件”。

这是Dify的有意设计:在私有化部署场景下,邮箱验证反而增加了运维复杂度。因此,注册时填写任意格式正确的邮箱(如admin@example.com)和强密码(至少8位,含大小写字母和数字),点击注册即可。系统会自动创建管理员账号,并赋予admin角色。

注册成功后,用同一邮箱和密码登录,你会进入Dify后台。此时,建议立即完成两件事:

  1. 修改管理员密码:点击右上角头像 → “个人设置” → “修改密码”。因为初始密码是你自己设定的,但为了安全,建议再设一次。

  2. 配置知识库默认模型:左侧菜单 → “设置” → “模型配置” → “Embedding Model”。Dify默认使用text-embedding-ada-002(OpenAI模型),但国内无法访问。你需要切换为开源模型,如BAAI/bge-m3(推荐,支持中英混合检索,精度高)。配置方法:

    • 模型提供商:选择Hugging Face
    • 模型名称:填BAAI/bge-m3
    • API Key:留空(Hugging Face模型无需Key)
    • 然后点击“测试连接”,看到绿色对勾即表示配置成功。

注意:BAAI/bge-m3模型需要额外下载,首次使用时会自动拉取,耗时约3-5分钟(取决于ECS带宽)。期间知识库上传会显示“处理中”,这是正常现象,不要重复点击。

4.3 首个知识库创建与文档上传实战

创建知识库是检验部署是否成功的黄金标准。点击左侧菜单“知识库” → “新建知识库”:

  • 知识库名称:填测试知识库
  • 描述:可选填
  • 模型:选择刚才配置好的BAAI/bge-m3
  • 分段设置:保持默认(段落长度500,重叠长度50)

点击“创建”后,进入知识库详情页。点击“上传文档”,选择一个PDF文件(建议用Dify官方文档dify-docs.pdf,约2MB,测试效果最佳)。

上传过程会经历三个阶段:

  • 解析:Dify调用unstructured库提取文本,耗时取决于PDF页数;
  • 分段:将长文本按规则切分成小段,每段送入Embedding模型;
  • 索引:将向量存入PostgreSQL的pgvector扩展表中。

整个过程在UI上显示为进度条。如果卡在某个阶段超过10分钟,说明后端服务异常。此时应检查worker容器日志:

docker logs docker-worker-1 | tail -n 50

常见报错是Connection refused,指向Redis连接失败,根源往往是.envREDIS_PASSWORDdocker-compose.yamlredis服务的REDIS_PASSWORD不一致。

上传成功后,点击“测试”按钮,在输入框中输入“Dify是什么”,点击发送。如果返回相关文档片段,说明知识库检索链路完全打通,你的Dify平台已具备生产可用的基础能力。

5. 常见问题与独家排障技巧:那些只有踩过坑才知道的真相

5.1 YAML格式报错:Top-level object must be a mapping的根因与修复

这是新手部署时最高频的报错,出现在执行docker compose up -d时,终端直接打印:

ERROR: yaml.parser.ParserError: while parsing a block mapping in "./docker-compose.yaml", line 1, column 1 expected <block end>, but found '<block mapping start>' in "./docker-compose.yaml", line 1, column 2

表面看是YAML语法错误,但绝大多数情况下,罪魁祸首是文件编码或不可见字符。具体来说,有三种可能:

  1. 文件开头的BOM(Byte Order Mark):某些Windows编辑器(如记事本)保存UTF-8文件时,会在开头插入3个字节的BOM(EF BB BF)。Linux的YAML解析器不认识这个标记,直接报错。修复方法:

    # 查看文件开头是否有BOM head -c 3 docker-compose.yaml | xxd # 如果输出是00000000: efbb bf,说明有BOM # 用vim去除BOM vim docker-compose.yaml :set nobomb :wq
  2. Tab键缩进:YAML规范严格禁止用Tab缩进,必须用空格。.env.example文件中有些行用Tab写了注释,如果复制时不小心带入了Tab,就会破坏缩进结构。修复方法:

    # 将所有Tab替换为2个空格 sed -i 's/\t/ /g' docker-compose.yaml
  3. 冒号后缺少空格:YAML要求key: value中冒号后必须有空格。比如image:nginx:latest(无空格)是非法的,必须是image: nginx:latest(有空格)。这个错误肉眼难辨,用以下命令批量修复:

    # 查找所有冒号后无空格的行,并自动添加空格 sed -i 's/:</: </g; s/:>/: >/g; s/:,/: ,/g; s/:}/: }/g; s/:]/: ]/g; s/:)/: )/g; s/:;/: ;/g; s/:\[/: [/g; s/:"/: "/g; s/:'\''/: '\''/g' docker-compose.yaml

实操心得:我现在的标准流程是,每次修改完docker-compose.yaml,都用yamllint工具扫描一遍。安装:pip3 install yamllint;扫描:yamllint docker-compose.yaml。它会精准定位到哪一行哪个字符出错,比看报错日志高效10倍。

5.2 Nginx容器反复Restarting:worker_processes无效的真相

docker ps中看到docker-nginx-1的状态是Restarting (1) 2 seconds ago,且docker logs docker-nginx-1输出类似:

2024/03/25 09:16:46 [emerg] 1#1: invalid number of arguments in "worker_processes" directive in /etc/nginx/nginx.conf:2 nginx: [emerg] invalid number of arguments in "worker_processes" directive in /etc/nginx/nginx.conf:2

这说明Nginx配置文件/etc/nginx/nginx.conf的第2行有语法错误。而这一行通常是:

worker_processes ${NGINX_WORKER_PROCESSES};

问题根源在于:.env文件中NGINX_WORKER_PROCESSES变量为空或格式错误。Docker Compose在替换${NGINX_WORKER_PROCESSES}时,如果变量未定义,会将其替换为空字符串,导致配置变成worker_processes ;,Nginx解析器认为参数缺失,于是报错。

但更深层的原因是:你手动修改了.env文件中的NGINX_WORKER_PROCESSES。Dify官方默认不定义这个变量,而是让Nginx使用auto模式(根据CPU核心数自动分配worker进程数)。如果你在.env中加了NGINX_WORKER_PROCESSES=4,就破坏了这个机制。

修复方案极其简单:打开.env文件,找到NGINX_WORKER_PROCESSES这一行,在行首加上#号注释掉它,保存退出,然后重启:

docker compose down docker compose up -d

注意:不要删除这一行,因为Dify的CI/CD流程会校验.env文件的完整性。注释是最安全的修改方式。

5.3 数据库连接拒绝:FATAL: password authentication failed for user "postgres"

这个报错出现在docker logs docker-api-1中,完整信息是:

psycopg2.OperationalError: FATAL: password authentication failed for user "postgres"

这表示api容器无法用密码登录PostgreSQL。原因只有一个:.env文件中的POSTGRES_PASSWORDdocker-compose.yamlpostgresql服务的POSTGRES_PASSWORD环境变量不一致。

但等等,docker-compose.yaml里明明写