本文记录了在 CentOS Stream 9 服务器上使用 Docker Compose 部署 Dify 的完整过程,包括踩过的所有坑和解决方案。
最近公司想搭建一套私有化的 AI 应用平台,选来选去最终选了 Dify。官方文档看起来很简单,docker-compose up -d 一把梭,但实际部署时却遇到了一堆问题。这篇文章不会告诉你"五分钟快速部署"的美梦,而是分享真实的部署过程和那些让人抓狂的错误。

在开始部署前,先理解 Dify 是怎么工作的。它不是一个单体应用,而是由多个容器协同工作:

这是 Dify 内部的反向代理,负责把请求分发到正确的服务:
/ → dify-web(前端页面)/api → dify-api(API 接口)/console/api → dify-api(控制台 API)/v1 → dify-api(应用 API)关键点:它不直接暴露到外网,而是通过 Nginx Proxy Manager(NPM)访问。这样做的好处是 SSL 证书、域名都由 NPM 统一管理。
这两个容器用的是同一个镜像 langgenius/dify-api,但通过环境变量 MODE 区分角色:
MODE=api):处理 HTTP 请求,负责用户交互MODE=worker):处理异步任务,比如训练模型、处理文件它们通过 Redis 的消息队列通信:

Redis 在 Dify 中扮演三个角色:
配置中使用了不同的 DB 隔离:
REDIS_DB: 0 # Session 存储
CELERY_BROKER_URL: redis://:密码@redis:6379/1 # 任务队列
CELERY_RESULT_BACKEND: redis://:密码@redis:6379/2 # 结果存储Dify 用了两个数据库:
dify:主业务数据(用户、应用、对话历史等)dify_plugin:插件系统专用数据库这个设计很巧妙,插件系统完全隔离,不会影响主业务。
这是个向量数据库,负责存储文档的 Embedding(向量表示)。当你问 AI 问题时,流程是这样的:

这是 Dify 0.4+ 版本引入的插件系统守护进程。它做两件事:
它有两个端口:
5002:HTTP API(给 dify-api 调用)5003:远程调试端口(gnet 协议)
部署前先规划好目录,这能避免很多麻烦:
/acowbo/docker-compose/dify/
├── docker-compose.yml # 核心编排文件
├── .env # 环境变量(包含密钥!)
├── nginx/ # Nginx 配置
│ ├── nginx.conf # 主配置
│ ├── proxy.conf # 代理设置
│ └── conf.d/
│ └── dify.conf # 路由规则
├── volumes/ # 持久化数据
│ ├── app/storage/ # 用户上传文件
│ ├── postgres/data/ # 数据库数据
│ ├── redis/data/ # Redis 持久化
│ ├── weaviate/data/ # 向量数据
│ ├── plugin-daemon/ # 插件文件
│ ├── sandbox/dependencies/ # 沙箱依赖
│ └── nginx/logs/ # 访问日志
├── cleanup-docker.sh # 清理脚本
├── logrotate-dify # 日志轮转
└── init.sh # 初始化脚本
官方文档可能会过时。我一开始配置的是:
image: langgenius/dify-api:1.10.0启动时报错:Error response from daemon: manifest for langgenius/dify-api:1.10.0 not found
去 Docker Hub 一看,根本没有 1.10.0 这个 tag!
解决方案:用 latest 标签,或者去 Docker Hub 确认版本。
image: langgenius/dify-api:latest我参考的文档里路径是 /opt/dify/dify-build,但我实际部署在 /acowbo/docker-compose/dify。结果 logrotate-dify 文件里写死了路径:
/opt/dify/dify-build/volumes/nginx/logs/*.log {
# 配置...
}导致日志轮转找不到文件。
经验教训:
./volumes)grep -r "你的路径" 全局检查我想让 Dify 和 Nginx Proxy Manager(NPM)共享网络 acowbo_network,一开始这样配置:
networks:
acowbo_network:
name: acowbo_network
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16启动时警告:network with name acowbo_network exists but was not created for project "dify"
原因是 NPM 已经创建了这个网络,Dify 不能重复创建。
正确做法:
networks:
acowbo_network:
external: true # 使用已存在的外部网络启动时报错:Bind for 0.0.0.0:80 failed: port is already allocated
因为 NPM 已经占用了 80 端口!其实 Dify 的 Nginx 根本不需要暴露端口,通过容器名访问就行。
解决方案:
nginx:
# ports: # 注释掉,不暴露端口
# - "80:80"NPM 中配置反向代理:
Domain: dify.acowbo.com
Forward to: http://dify-nginx:80 # 用容器名流程图:

我把 map 指令放在了 proxy.conf 里:
# proxy.conf(错误位置)
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}启动报错:nginx: [emerg] "map" directive is not allowed here
map 指令只能放在 http 块,而 proxy.conf 是被 include 到 server 块的。
正确做法:
# nginx.conf
http {
# map 指令必须在这里
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
include /etc/nginx/proxy.conf;
include /etc/nginx/conf.d/*.conf;
}这是最折磨人的部分,花了几个小时调试。
日志显示:[PANIC]Invalid configuration: plugin remote installing host is empty
缺少环境变量。查官方文档和源码后,发现需要配置:
environment:
SERVER_KEY: ${SECRET_KEY} # 认证密钥
SERVER_PORT: 5002 # HTTP API 端口
PLUGIN_REMOTE_INSTALLING_HOST: 0.0.0.0 # 调试服务地址
PLUGIN_REMOTE_INSTALLING_PORT: 5003 # 调试端口
PLUGIN_WORKING_PATH: /app/storage/cwd # 工作目录
# 数据库配置
DB_DATABASE: dify_plugin # 注意:不是 dify!plugin-daemon 有两个服务:
我一开始把两个端口搞混了,导致 API 连接失败。
关键配置:
# plugin-daemon
environment:
SERVER_PORT: 5002 # GIN HTTP 端口
PLUGIN_REMOTE_INSTALLING_PORT: 5003 # gnet 调试端口
# dify-api
environment:
PLUGIN_DAEMON_URL: http://plugin-daemon:5002 # 连接 HTTP 端口日志验证:
[gnet] Launching gnet with 8 event-loops, listening on: tcp://0.0.0.0:5003
[GIN] 200 | GET "/health/check" # HTTP 服务在 5002这个最坑!我配置了 PLUGIN_DAEMON_ENDPOINT,但 Dify 代码里认的是 PLUGIN_DAEMON_URL!
测试:
docker exec dify-api python -c \
"from configs import dify_config; print(dify_config.PLUGIN_DAEMON_ENDPOINT)"
# 报错:AttributeError: 'DifyConfig' object has no attribute 'PLUGIN_DAEMON_ENDPOINT'
# Did you mean: 'PLUGIN_DAEMON_TIMEOUT'?正确的环境变量:
# .env 文件
PLUGIN_DAEMON_URL=http://plugin-daemon:5002 # 不是 ENDPOINT!
PLUGIN_DAEMON_KEY=你的密钥配置都对了,但请求返回 401 Unauthorized。
测试发现认证方式不对:
# 错误(Bearer Token)
curl -H "Authorization: Bearer 密钥" http://plugin-daemon:5002/plugin/xxx
# 401 Unauthorized
# 正确(X-Api-Key)
curl -H "X-Api-Key: 密钥" http://plugin-daemon:5002/plugin/xxx
# 200 OK查看源码确认了认证方式:
# api/core/plugin/impl/base.py
prepared_headers["X-Api-Key"] = dify_config.PLUGIN_DAEMON_KEY第一次访问页面,所有 /console/api/ 接口都返回 400:
sqlalchemy.exc.ProgrammingError: relation "dify_setups" does not exist原因是数据库表还没创建。
解决方案:
# 运行数据库迁移
docker exec dify-api flask db upgrade
# 重启服务
docker compose restart api worker然后访问 https://你的域名/install 完成初始化,创建管理员账号。
40GB 系统盘很容易被日志撑爆!必须做日志管理。
services:
api:
logging:
driver: "json-file"
options:
max-size: "50m" # 单文件最大 50MB
max-file: "2" # 保留 2 个文件
compress: "true" # 压缩旧日志# /etc/logrotate.d/dify
/acowbo/docker-compose/dify/volumes/nginx/logs/*.log {
daily # 每天轮转
rotate 7 # 保留 7 天
maxsize 100M # 超过 100M 强制轮转
compress # 压缩
delaycompress # 延迟一天压缩
missingok # 文件不存在不报错
notifempty # 空文件不轮转
postrotate
docker exec dify-nginx nginx -s reopen
endscript
}部署:
sudo cp logrotate-dify /etc/logrotate.d/dify
sudo chmod 644 /etc/logrotate.d/dify
sudo logrotate -d /etc/logrotate.d/dify # 测试# cleanup-docker.sh
docker system prune -f --volumes --filter "until=168h" # 删除 7 天前的数据
docker image prune -a -f --filter "until=168h"
# crontab
0 3 * * 0 /acowbo/docker-compose/dify/cleanup-docker.shservices:
api:
image: langgenius/dify-api:latest
container_name: dify-api
restart: always
environment:
TZ: Asia/Hong_Kong # 时区
MODE: api
SECRET_KEY: ${SECRET_KEY}
# 数据库
DB_USERNAME: ${DB_USERNAME:-postgres}
DB_PASSWORD: ${DB_PASSWORD}
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: dify
# Redis
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
# 插件配置(关键!)
PLUGIN_ENABLED: true
PLUGIN_DAEMON_URL: http://plugin-daemon:5002 # 注意是 URL 不是 ENDPOINT
PLUGIN_DAEMON_KEY: ${SECRET_KEY}
volumes:
- ./volumes/app/storage:/app/api/storage
networks:
- acowbo_network
depends_on:
- postgres
- redis
plugin-daemon:
image: langgenius/dify-plugin-daemon:0.4.1-local
container_name: dify-plugin-daemon
restart: always
environment:
TZ: Asia/Hong_Kong
# 服务配置
SERVER_PORT: 5002 # HTTP API
SERVER_KEY: ${SECRET_KEY}
# 调试配置
PLUGIN_REMOTE_INSTALLING_HOST: 0.0.0.0
PLUGIN_REMOTE_INSTALLING_PORT: 5003 # gnet 调试端口
PLUGIN_WORKING_PATH: /app/storage/cwd
# 数据库(独立库)
DB_USERNAME: ${DB_USERNAME:-postgres}
DB_PASSWORD: ${DB_PASSWORD}
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: dify_plugin # 注意:不是 dify
# Redis
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
volumes:
- ./volumes/plugin-daemon:/app/storage
networks:
- acowbo_network
depends_on:
- postgres
networks:
acowbo_network:
external: true # 与 NPM 共享网络# ========================================
# 密钥配置(必须修改!)
# ========================================
SECRET_KEY=<生成随机密钥>
DB_PASSWORD=<生成随机密码>
REDIS_PASSWORD=<生成随机密码>
WEAVIATE_API_KEY=<生成随机密钥>
# ========================================
# 域名配置
# ========================================
CONSOLE_WEB_URL=https://your_domain.com
CONSOLE_API_URL=https://your_domain.com
APP_WEB_URL=https://your_domain.com
# ========================================
# 插件配置(关键!)
# ========================================
PLUGIN_ENABLED=true
PLUGIN_DAEMON_URL=http://plugin-daemon:5002 # 不是 ENDPOINT
PLUGIN_DAEMON_KEY=${SECRET_KEY}
# ========================================
# 数据库性能优化(16GB 内存)
# ========================================
POSTGRES_SHARED_BUFFERS=2GB
POSTGRES_EFFECTIVE_CACHE_SIZE=8GB
REDIS_MAXMEMORY=512mb生成随机密钥:
# SECRET_KEY(64 字符)
openssl rand -base64 48
# 密码(32 字符)
openssl rand -base64 24# 1. 创建目录并上传文件
mkdir -p /acowbo/docker-compose/dify
cd /acowbo/docker-compose/dify
# 上传 docker-compose.yml, .env, nginx/ 等文件
# 2. 生成密钥并修改 .env
vi .env
# 修改所有密钥和域名
# 3. 创建数据目录
./init.sh # 或手动创建 volumes/ 下的目录
# 4. 确保 Docker 网络存在(如果用外部网络)
docker network create acowbo_network || true
# 5. 启动服务
docker compose up -d
# 6. 查看状态
docker compose ps
# 7. 初始化数据库(第一次部署)
docker exec dify-api flask db upgrade
# 8. 重启确保配置生效
docker compose restart api worker
# 9. 访问初始化页面
https://你的域名/installDomain Names: dify.acowbo.com
Scheme: http
Forward Hostname/IP: dify-nginx
Forward Port: 80
☑ Cache Assets
☑ Block Common Exploits
☑ Websockets Support ← 必须勾选!SSL Certificate: Request a new SSL Certificate
☑ Force SSL
☑ HTTP/2 Support
☑ HSTS Enabled
Email: your@email.com
☑ I Agree to the Let's Encrypt Terms of Service# AI 响应可能较慢,增加超时
proxy_read_timeout 600s;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
# 支持文件上传
client_max_body_size 100M;
# 传递真实 IP
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;
# 查看日志
docker logs 容器名 --tail 100
# 常见原因:
# - 环境变量缺失
# - 数据库连接失败
# - 端口被占用
# - 配置文件语法错误# 检查容器状态
docker compose ps
# 检查网络连通性
docker exec dify-nginx curl http://api:5001/health
docker exec dify-nginx curl http://web:3000/
# 检查 NPM 到 dify-nginx
docker exec npm容器名 curl http://dify-nginx/health这是最常见的错误,检查清单:
# 1. 确认环境变量名正确
docker exec dify-api env | grep PLUGIN
# 应该看到:
# PLUGIN_DAEMON_URL=http://plugin-daemon:5002 (不是 ENDPOINT)
# PLUGIN_DAEMON_KEY=...
# 2. 测试连接
docker exec dify-api curl -H "X-Api-Key: 你的密钥" \
http://plugin-daemon:5002/health/check
# 3. 查看 plugin-daemon 日志
docker logs dify-plugin-daemon --tail 50
# 4. 确认数据库存在
docker exec dify-postgres psql -U postgres -l | grep plugin# relation "xxx" does not exist
# → 运行迁移
docker exec dify-api flask db upgrade
# password authentication failed
# → 检查 .env 中的 DB_PASSWORD
# could not connect to server
# → 检查 postgres 容器是否启动
docker compose ps postgres# .env
POSTGRES_SHARED_BUFFERS=2GB # 共享缓冲区
POSTGRES_EFFECTIVE_CACHE_SIZE=8GB # 系统缓存
POSTGRES_WORK_MEM=16MB # 排序/JOIN 内存
POSTGRES_MAINTENANCE_WORK_MEM=512MB # 维护操作内存
POSTGRES_MAX_CONNECTIONS=200 # 最大连接数REDIS_MAXMEMORY=512mb # 最大内存
REDIS_MAXMEMORY_POLICY=allkeys-lru # 淘汰策略# Celery 并发
CELERY_WORKER_CLASS=gevent # 使用协程
CELERY_MAX_WORKERS=8 # 根据 CPU 核心数# 实时查看所有容器日志
docker compose logs -f
# 只看特定服务
docker compose logs -f api worker
# 查看最近 100 行
docker logs dify-api --tail 100
# 查看最近 5 分钟
docker logs dify-api --since 5m
# 过滤错误
docker logs dify-api 2>&1 | grep -i error#!/bin/bash
# backup.sh
BACKUP_DIR="/backup/dify-$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
# 备份数据库
docker exec dify-postgres pg_dump -U postgres dify > $BACKUP_DIR/dify.sql
docker exec dify-postgres pg_dump -U postgres dify_plugin > $BACKUP_DIR/dify_plugin.sql
# 备份向量数据
docker exec dify-weaviate curl -X POST http://localhost:8080/v1/backups/filesystem \
-H "Content-Type: application/json" \
-d '{"id":"backup-'$(date +%Y%m%d)'"}'
# 备份上传文件
tar -czf $BACKUP_DIR/storage.tar.gz volumes/app/storage/
# 删除 7 天前的备份
find /backup -name "dify-*" -mtime +7 -exec rm -rf {} \;定时任务:
# crontab -e
0 2 * * * /acowbo/docker-compose/dify/backup.sh40GB 系统盘必须时刻盯着:
# 查看磁盘使用
df -h
# 查看 Docker 占用
docker system df
# 查看各目录大小
du -sh volumes/*
# 查看日志大小
du -sh volumes/nginx/logs/
du -sh /var/lib/docker/containers/*/告警脚本:
#!/bin/bash
# disk-alert.sh
USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ $USAGE -gt 80 ]; then
echo "磁盘使用率: $USAGE% - 告警!" | mail -s "Dify 磁盘告警" admin@example.com
# 清理 Docker
docker system prune -f
fi# NPM 管理端口别用 81
ports:
- "10811:81" # 用随机高端口# 只开放必要端口
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --permanent --add-port=10811/tcp # NPM 管理
firewall-cmd --reload
# 限制 NPM 管理端口访问
firewall-cmd --permanent --add-rich-rule='
rule family="ipv4"
source address="你的IP"
port port="10811" protocol="tcp"
accept'# 拉取最新镜像
docker compose pull
# 重启服务
docker compose up -d
# 清理旧镜像
docker image prune -a -f --filter "until=168h"部署完成后的界面:

访问地址:https://your_domain.com
功能验证清单:
这次部署从下午 1 点折腾到晚上 9 点,踩了无数坑。总结几点经验:
不要一上来就 docker-compose up,先搞清楚每个容器的作用和依赖关系。Dify 的微服务架构并不复杂,但容器间的交互需要弄明白。
PLUGIN_DAEMON_URL vs PLUGIN_DAEMON_ENDPOINT)遇到问题别慌,看日志:
docker logs 容器名 --tail 100 --follow90% 的问题日志里都有答案。
尤其是 plugin-daemon 这种新功能,文档更新不及时。遇到问题要:
docker exec 进容器探索建议:
数据库没备份,睡觉都不踏实。至少做到:
Dify 是个很棒的 AI 应用平台,但部署确实不算"开箱即用"。希望这篇文章能帮到想要自建的朋友,少踩点坑。
如果你也遇到了部署问题,欢迎交流讨论。记住:日志是你最好的朋友,耐心是你最大的武器。
参考资料:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。