8 - Docker 快速入门与 Dify 部署排障

第 6 章已经带你在 Windows 上跑过 Coze Studio、Dify、Coze Loop,第 7 章会继续讲企业级大模型部署。本章回头补 Docker 这层基础。后面排查 Dify、迁移服务、升级版本时,经常都要回到 Docker 层看状态、日志和数据挂载。

刚开始做 Dify 私有化部署时,问题经常被简单归成“Dify 装不上”。实际排查下来,卡住的地方往往更基础:镜像和容器分不清,宿主机端口和容器端口分不清,不知道 volume 会不会被删,也看不懂为什么一次启动会拉起一组容器。升级后如果进入初始化页面,问题通常也不在页面本身,而是数据库、挂载目录或 .env 没对上。

这一章只抓部署 Dify 和后续实战项目最常用的那条线:镜像怎么交付环境,容器怎么运行服务,Compose 怎么拉起多服务,volume 怎么保存数据,日志和备份怎么辅助排障。

官方文档与资源:详见 工具导航与参考资料索引 - 部署与基础设施


阅读路径

现在遇到的问题 建议先看
还没安装 Docker Desktop 第 2.2~2.5 小节:Docker Desktop 安装与验证
想在 Ubuntu 服务器安装 Docker 第 2.10 小节:Ubuntu 下 Docker 安装与卸载
不知道 Docker 到底是什么 第 1.2 小节:Docker 是什么
分不清 Docker 和虚拟机 第 1.3 小节
分不清镜像、容器、仓库、数据卷 第 1.5 小节
想先跑一个最小 Docker 案例 第 2 部分:Docker 基础命令与第一个容器
不知道 Dockerfile 和 Compose 区别 第 3 部分:从单容器到 Compose 多服务编排
担心数据存放位置或误删数据 第 4 部分:数据持久化、网络、迁移与危险边界
想把 Windows 环境迁移到 Ubuntu 第 4.5 小节:跨机器迁移与离线同步
想知道 Dify 到底跑了哪些容器 第 5 部分:把 Docker 知识迁移到 Dify 部署结构
页面打不开,容器状态不对 第 6 部分:Dify 排障:状态、日志、数据与数据库连接
想用 Navicat 连接 Dify 数据库 第 6.5 小节
准备升级 Dify 第 7 部分:Dify 升级、备份与源码改造
改了 Dify 前端源码但没生效 第 7.2 小节
准备清理 Docker 或重装环境 第 8.6 小节:危险操作清单
只想查命令 第 8 部分:Docker 常用命令速查

1、为什么要学 Docker

1.1 环境不一致的问题

在没有 Docker 之前,部署一个项目往往不是“把代码放到服务器上”这么简单。

开发电脑上能跑,测试环境可能报错;测试环境能跑,生产服务器又缺少依赖;换一台机器,就要重新安装 Python、Node.js、数据库、系统库、驱动、配置文件。项目越多,版本越多,环境差异就越难控制。

Docker 解决的就是这个老问题:把应用和运行环境一起打包,换一台机器也尽量按同一种方式运行

放到 Dify、RAG 或智能体项目里,这个问题会更具体。你在浏览器里看到的是一个页面,实际运行时还会涉及后端接口、后台任务、数据库、Redis、向量库、Nginx、Sandbox、插件服务等组件。它们的版本、端口、数据目录和启动顺序只要有一处没对上,排障就很容易跑偏。

Docker 的价值在这里很明确:把这些组件放进一套相对固定的部署流程里。先让环境能稳定复现,再谈迁移、升级和排障。

1.2 Docker 是什么

Docker 是一个开源的应用容器引擎,底层主要基于 Go 语言开发。你可以把它理解成一个“打包并运行环境”的工具:把应用、依赖、配置和运行环境放进一个轻量、可移植、相对隔离的容器里。

Docker 官方曾经用一句话概括它的目标:

1
Build, Ship and Run Any App, Anywhere

它强调的是构建、分发、运行这一整条链路。一些教程里说的“一次封装,到处运行”,讲的就是这个意思。

和手工安装环境相比,Docker 的价值不只是少装几个软件,而是把应用交付从零散的安装步骤变成可复制的流程:

能力 说明 课程中的例子
可移植性 同一套镜像可以在本地、测试环境、服务器上运行 本地跑通的 MySQL / Redis / 向量库迁到 Ubuntu
隔离性 不同容器有各自的进程、文件系统和依赖 Dify 的 apiworkerredis 互不直接污染环境
低开销 容器共享宿主机内核,通常比完整虚拟机更轻 一台机器上同时跑多个基础服务
可伸缩性 多实例服务可以更容易复制和扩展 后续生产部署中扩展 worker 处理后台任务
可复现性 环境由镜像、Compose、环境变量描述 新同学拉起同一套 Dify 环境,排障口径一致

当然,Docker 不是万能药。数据库备份、显卡驱动、网络代理、数据权限、云服务器安全组,这些还是要自己处理。但它能把“应用怎么跑”这件事先稳住,这对后面部署 Dify、本地模型和企业级项目很关键。

1.3 Docker 和虚拟机的区别

刚接触 Docker 时,可以暂时把它理解成“比虚拟机更轻的运行环境”。这个说法方便入门,但不够准确。

虚拟机会把操作系统也虚拟出来,每个虚拟机都有自己的完整系统;Docker 容器共享宿主机内核,只把应用运行需要的文件、依赖和隔离环境打包起来。

Docker 与虚拟机在架构层面的区别

主要看三个区别:

对比项 虚拟机 VM Docker 容器
隔离方式 虚拟化整套操作系统 共享宿主机内核,隔离进程和文件系统
启动速度 通常较慢 通常更快
资源占用 更高 更低
适合场景 强隔离、多系统环境、传统基础设施 应用交付、开发测试、微服务部署

因此,Docker 不是用来替代所有虚拟机的工具。它更适合解决应用交付、环境一致、多服务编排这类问题。

1.4 Docker 架构

Docker 是典型的客户端和服务端架构。

你在终端里执行的 docker 命令,是 Docker Client;负责创建容器、拉取镜像、管理网络和数据卷的是 Docker Daemon。镜像通常来自 Docker Hub 或其他镜像仓库。

Docker 客户端、Docker Daemon 与 Registry 的关系

图里重点画出了四个对象:客户端、Docker 主机、镜像仓库、容器。为了后面能读懂 Dify 的 Compose 文件,这里再把 volume 和 Compose 一起补进来:

对象 和谁发生关系 关系说明
Docker Client(Docker 客户端) Docker Daemon 终端里的 docker 命令会把操作请求发送给 Docker Daemon
Docker Daemon(Docker 守护进程 / 服务端) Registry、Image、Container、Volume 负责拉取镜像、创建容器、管理网络和数据卷
Registry(镜像仓库) Image 存放和分发镜像,例如 Docker Hub 或企业私有仓库
Image(镜像) Container 容器基于镜像创建并运行;一个镜像可以启动多个容器
Container(容器) Image、Volume、Network 运行应用进程,并把需要持久化的数据写入 volume 或挂载目录
Volume(数据卷) Container 保存数据库、上传文件、索引等数据,通常独立于容器生命周期
Docker Compose(多容器编排工具) Image、Container、Volume、Network 用一份 YAML 把镜像、容器、数据卷、网络和环境变量统一描述出来

所以执行一条命令时,要知道它在操作谁:

  • docker pull:从仓库拉镜像。
  • docker run:基于镜像创建并启动容器。
  • docker ps:查看容器。
  • docker images:查看镜像。
  • docker volume ls:查看数据卷。
  • docker compose up -d:按配置启动一组服务。

1.5 镜像、容器、仓库、数据卷

可以这样理解 常见例子
Docker Engine 运行容器的引擎 Docker Desktop、服务器上的 Docker 服务
镜像 Image 应用的运行模板,像安装包 mysql:8.0.45postgres:15-alpine
容器 Container 镜像运行后的实例 mysql-dbdocker-api-1
仓库 Repository 存放和下载镜像的平台 Docker Hub、企业私有镜像仓库
数据卷 Volume 独立于容器生命周期的数据存储 mysql_datapostgres_data
Dockerfile 自定义镜像的构建脚本 FROM python:3.12
Docker Compose 用一份 YAML 管理一组容器 docker-compose.yaml
bind mount 把宿主机目录挂到容器里 ./volumes/db/data:/var/lib/postgresql/data

可以这样理解:镜像负责提供程序,容器负责把程序跑起来,仓库负责分发镜像,volume 负责保存数据,Compose 负责把一组容器编排起来。

1.6 Dockerfile 和 Compose

Dockerfile 和 Compose 经常一起出现,但它们解决的问题不同。

Dockerfile 负责构建镜像。比如你想做一个包含 Python 3.12、依赖包和业务脚本的环境,就写 Dockerfile,让 Docker 自动构建。

Docker Compose 负责运行一组服务。比如一个项目需要同时启动 MySQL、Python 应用、Redis、Nginx,就用 docker-compose.yaml 把它们的镜像、端口、环境变量、网络和数据卷写清楚。

后面 Dify 的部署正是这个思路:官方已经帮我们准备好镜像和 Compose 文件,我们执行 docker compose up -d,就能把 web、api、worker、数据库、Redis、向量库、nginx 等服务一起拉起来。


2、Docker 安装:本地 Windows 与服务器 Ubuntu

2.1 先判断你要装在哪里

学习 Docker 时,通常会碰到两种环境:

环境 推荐安装方式 适合场景
Windows 本地电脑 Docker Desktop + WSL 2 学习、演示、跑 Dify / Coze Studio 本地版
Ubuntu 云服务器 Docker Engine + Compose 插件 企业部署、远程服务、后续项目上线

如果只是跟着本章学习,先装 Docker Desktop 就够了;如果已经在准备云服务器部署,可以直接跳到第 2.10 小节。

两种环境里的常用命令差不多,差别主要在安装方式、文件路径、权限和镜像源配置。

2.2 Windows 安装前准备

Windows 本地部署 Coze Studio、Dify、Coze Loop 这类平台时,最常用的方案是 Docker Desktop + Docker Compose

安装前先看几件事:

  • Windows 10 / Windows 11 系统。
  • 已开启虚拟化功能。大部分新电脑默认开启,如果 Docker Desktop 启动时报虚拟化错误,再进入 BIOS 检查。
  • 可以正常使用 WSL 2。Docker Desktop 在 Windows 上通常依赖 WSL 2 来运行 Linux 容器。
  • 本机网络能访问 Docker 官网和镜像仓库;如果镜像拉取慢,后面第 4.4 小节再处理。

本章不要求你提前安装 MySQL、Redis、Nginx。后面会直接用 Docker 镜像启动。

Windows 上可以按下面顺序检查:

检查项 怎么看 说明
系统版本 Win + R 输入 winver 建议 Windows 10 2004 及以上,Windows 11 更省心
虚拟化 任务管理器 -> 性能 -> CPU -> 虚拟化 如果未启用,需要进 BIOS 开启 Virtualization Technology
WSL 功能 “启用或关闭 Windows 功能” 勾选“适用于 Linux 的 Windows 子系统”和“虚拟机平台”
WSL 内核 Docker Desktop 启动提示或 wsl --status 旧环境可能需要更新 WSL 内核

虚拟化可以在任务管理器里看,确认状态显示为“已启用”:

在任务管理器中确认 CPU 虚拟化已启用

WSL 2 相关功能可以在 Windows 功能里勾选。不同 Windows 版本的显示文字可能略有差异,重点是启用 Virtual Machine Platform 和 WSL:

在 Windows 功能中启用虚拟机平台和适用于 Linux 的 Windows 子系统

如果需要用命令启用 WSL 和虚拟机平台,可以用管理员 PowerShell 执行:

1
2
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

执行后需要重启电脑。WSL 内核可从 Microsoft WSL 发布页下载更新包;如果是 ARM 架构设备,要选择对应架构的安装包。

2.3 下载 Docker Desktop

进入 Docker Desktop 官方下载页:

1
https://www.docker.com/products/docker-desktop/

选择 Windows 版本下载安装包。

Docker Desktop 官方下载页面

2.4 安装并启动 Docker Desktop

双击安装包,按提示完成安装即可。安装过程中如果看到 WSL 2、桌面快捷方式等选项,保持默认配置即可。

Docker Desktop 安装设置界面

安装完成后打开 Docker Desktop,确认状态栏显示 Running

Docker Desktop 成功启动后的主界面

如果 Windows 提示需要安装“适用于 Linux 的 Windows 子系统”,按提示装完,再重新打开 Docker Desktop。

2.5 验证 Docker 环境

打开 PowerShell、CMD、Windows Terminal 或 Docker Desktop 自带终端,依次执行:

1
2
3
docker --version
docker compose version
docker run hello-world

能正常输出版本,并且 hello-world 能运行,说明 Docker 基础环境可用。

Windows + Docker Desktop 场景下,官方也建议把 Linux 容器使用的源码和挂载数据放在 WSL 的 Linux 文件系统里,而不是长期放在 Windows 文件系统路径下。这样通常会减少文件同步、权限和性能问题。

如果 Docker Desktop 安装失败、启动异常,或者你想彻底重装,可以按下面思路清理。注意,这会删除 Docker Desktop 管理的镜像、容器和数据,执行前先确认没有重要数据:

1
2
3
4
5
6
7
8
9
# 1. 停止所有 WSL 实例
wsl --shutdown

# 2. 查看 WSL 发行版
wsl --list --verbose

# 3. 删除 Docker Desktop 相关发行版
wsl --unregister docker-desktop
wsl --unregister docker-desktop-data

还可以手动检查这些残留目录:

1
2
3
C:\Users\你的用户名\.docker
C:\Users\你的用户名\AppData\Local\Docker
C:\Program Files\Docker

普通卸载只需要在 Windows “应用和功能”里卸载 Docker Desktop;前面这种清理更接近“彻底重装”,不要把它当成日常排障第一步。

2.6 启动 MySQL 容器

先拉取 MySQL 镜像:

1
docker pull mysql:8.0.45

查看本地镜像:

1
docker images

使用 docker images 查看本地 MySQL 镜像

启动一个 MySQL 容器:

1
docker run -d --name mysql-db -p 9999:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0.45

这条命令里最重要的是几个参数:

参数 含义
-d 后台运行,不占用当前终端
--name mysql-db 给容器起名,后续可以用这个名字 stop、logs、exec
-p 9999:3306 宿主机 9999 端口映射到容器内 3306 端口
-e MYSQL_ROOT_PASSWORD=123456 给 MySQL 容器注入环境变量,设置 root 密码
mysql:8.0.45 要运行的镜像名和版本

查看容器:

1
docker ps

使用 docker ps 查看正在运行的 MySQL 容器

这里有两个地址容易混:

  • 本机访问 MySQL:127.0.0.1:9999
  • 容器内部 MySQL 实际监听:3306

也就是 -p 9999:3306 左边给宿主机访问,右边给容器内部服务使用。

2.7 Python 连接 MySQL

如果本机已经有 Python 环境,可以安装依赖:

1
pip install pymysql cryptography

新建 test_mysql.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pymysql

conn = pymysql.connect(
host="127.0.0.1",
port=9999,
user="root",
password="123456",
database="mysql",
charset="utf8mb4",
cursorclass=pymysql.cursors.DictCursor,
)

with conn:
with conn.cursor() as cursor:
cursor.execute("SELECT VERSION() AS version")
row = cursor.fetchone()
print(f"MySQL 版本:{row['version']}")

print("Python 已成功连接 Docker 中的 MySQL")

运行:

1
python test_mysql.py

本机 Python 脚本成功连接 Docker 中的 MySQL

这个小案例不是为了讲 MySQL,而是先把 Docker 的基本动作串起来:

  1. 镜像从仓库下载。
  2. 容器基于镜像运行。
  3. 宿主机通过端口映射访问容器服务。

后面访问 Dify 的 nginx、PostgreSQL、Redis,用的仍然是这套逻辑。

2.8 从 docker ps 区分镜像和容器

执行:

1
docker ps

命令行里看到的容器列表大致是这样的:

docker ps 输出中 IMAGE 列与 NAMES 列的区别

输出里常见两列:

含义 示例
IMAGE 镜像名 postgres:15-alpinelanggenius/dify-api:<version>
NAMES 容器名 docker-db_postgres-1docker-api-1

看输出时可以先抓两个特征:

  • :tag 的通常是镜像,例如 redis:6-alpine
  • docker-api-1mysql-dbqdrant 这种运行中的名字通常是容器。

Docker Desktop 里也能直接区分:

Docker Desktop 中的 Containers 列表

Docker Desktop 中的 Images 列表

一个镜像可以启动多个容器。Dify 的 apiworkerworker_beat 就可能使用同一个后端镜像,只是启动模式不同。

2.9 基础命令速查

目的 命令 说明
查看 Docker 版本 docker --version 检查 Docker CLI 是否可用
查看 Compose 版本 docker compose version 检查 Compose v2 是否可用
拉取镜像 docker pull mysql:8.0.45 从镜像仓库下载镜像
查看镜像 docker images 查看本地镜像
启动容器 docker run ... 基于镜像创建并启动容器
查看运行中容器 docker ps 只看运行中的容器
查看全部容器 docker ps -a 包括已停止容器
查看日志 docker logs -f mysql-db 跟踪某个容器日志
进入容器 docker exec -it mysql-db sh 进入容器内部
停止容器 docker stop mysql-db 停止运行中的容器
删除容器 docker rm mysql-db 删除已停止的容器
删除镜像 docker rmi mysql:8.0.45 删除本地镜像

日常排障时,先看 docker ps,再看 docker logs。在没确认数据位置之前,先别急着删除容器或清理 volume。

2.10 Ubuntu 下 Docker 安装与卸载

本地学习阶段通常用 Windows + Docker Desktop;正式上服务器时,更常见的是 Ubuntu + Docker Engine。第 7 章的云服务器部署、后面的电商问数和深度研搜项目,都会先遇到同一个前提:服务器上要有可用的 Docker 和 Compose。

Ubuntu 上安装 Docker,建议优先参考 Docker 官方文档。下面给出课程里最常用的一条路径,适合新服务器初始化:

1
2
3
4
5
6
7
8
9
# 1. 卸载系统里可能存在的旧版本
sudo apt remove -y docker docker-engine docker.io containerd runc

# 2. 按 Docker 官方 Ubuntu 安装文档配置仓库并安装
# 官方文档:https://docs.docker.com/engine/install/ubuntu/

# 3. 验证 Docker 和 Compose
docker --version
docker compose version

如果执行 docker 命令时提示权限不足,可以把当前用户加入 docker 用户组:

1
sudo usermod -aG docker $USER

这条命令执行完后,需要退出当前 SSH 会话并重新登录,权限才会生效。不要看到命令没立刻生效就反复执行。

服务器拉镜像慢时,可以配置 Docker Engine 镜像源。Ubuntu 上配置文件通常是:

1
sudo vim /etc/docker/daemon.json

示例配置:

1
2
3
4
5
6
7
{
"registry-mirrors": [
"https://docker.xuanyuan.me",
"https://docker.1ms.run",
"https://docker.m.daocloud.io"
]
}

保存后重启 Docker:

1
2
3
sudo systemctl daemon-reload
sudo systemctl restart docker
docker info

docker info 输出中看到 Registry Mirrors,说明配置已经被 Docker Engine 读取。

镜像源是易变资源,今天可用不代表一直可用。生产环境更稳的方式是使用企业私有镜像仓库、云厂商容器镜像服务,或者提前做离线镜像包。

如果 Docker 安装失败、版本混乱,或你确实要彻底清理服务器上的 Docker,可以按下面方式卸载。注意,这会删除本机容器、镜像和 volume 数据,执行前先确认没有重要服务运行:

1
2
3
4
5
sudo systemctl stop docker
sudo apt remove -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd
docker --version

如果只是普通升级或排障,不要轻易删除 /var/lib/docker。这个目录里可能保存着数据库 volume、向量库数据和上传文件。


3、从单容器到 Compose 多服务编排

第 2 部分的 MySQL 示例只用了 docker run,适合快速体验。到了真实项目里,一个容器通常不够。Dify 有 web、api、worker、数据库、Redis、向量库、nginx、sandbox、plugin_daemon 等服务;电商问数项目里也会同时依赖 MySQL、Qdrant、Elasticsearch 和后端服务。

这一部分就往前走一步:不再手动敲一长串 docker run,而是用文件描述一组服务。

阶段 解决的问题 典型文件或命令
docker run 快速启动一个现成镜像 docker run -d ... mysql:8.0.45
Dockerfile 把自己的代码和依赖打成镜像 FROM python:3.12
Compose 一次启动多容器项目 docker compose up -d

Dify 的 docker/ 目录也可以按这个思路理解:它不是普通源码目录,更像一份多服务部署说明书。

3.1 Dockerfile

刚才的 MySQL 是直接使用官方镜像。如果我们想把自己的 Python 脚本、依赖和运行环境也打包成镜像,就需要 Dockerfile。

在项目目录里新建 Dockerfile

1
2
3
4
5
6
7
8
9
FROM python:3.12

WORKDIR /app

COPY . /app

RUN pip install pymysql cryptography

CMD ["python", "test_mysql1.py"]

逐行理解:

指令 作用
FROM python:3.12 以 Python 3.12 官方镜像为基础
WORKDIR /app 设置容器内工作目录
COPY . /app 把当前项目文件复制进镜像
RUN pip install ... 构建镜像时安装依赖
CMD [...] 容器启动时默认执行的命令

Dockerfile 解决的是“如何构建一个符合我项目需要的镜像”。它不是用来启动一组服务的,启动多服务要交给 Compose。

3.2 Compose

Docker Compose 用一份 YAML 文件描述多容器应用。Docker 官方文档也把 Compose 定义为管理多容器应用的工具,它可以在一个配置文件里统一管理 services、networks、volumes。

常见部署文档会让你进入 docker 目录,然后执行:

1
docker compose up -d

它会按 docker-compose.yaml 里的配置做几件事:

  1. 拉取或构建镜像。
  2. 创建容器。
  3. 创建网络。
  4. 挂载数据目录或 volume。
  5. 后台启动服务。

所以这条命令不是“启动一个程序”,而是“把一组互相依赖的服务一起拉起来”。

3.3 MySQL + Python 的 Compose 小案例

项目目录可以这样放:

1
2
3
4
docker-python-mysql/
├── docker-compose.yml
├── Dockerfile
└── test_mysql1.py

docker-compose.yml 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
services:
mysql-db:
image: mysql:8.0.45
restart: always
environment:
MYSQL_ROOT_PASSWORD: "123456"
MYSQL_DATABASE: "test_db"
MYSQL_INITDB_ARGS: "--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci"
ports:
- "9999:3306"
volumes:
- mysql_data:/var/lib/mysql
networks:
- app-network
healthcheck:
test:
["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p123456"]
interval: 3s
timeout: 3s
retries: 10
start_period: 5s

python-app:
build: .
depends_on:
mysql-db:
condition: service_healthy
environment:
MYSQL_HOST: "mysql-db"
MYSQL_PORT: "3306"
MYSQL_USER: "root"
MYSQL_PASSWORD: "123456"
MYSQL_DB: "test_db"
networks:
- app-network

networks:
app-network:
driver: bridge

volumes:
mysql_data:

这份 Compose 里有几处值得多看一眼:

  • python-app 通过 build: . 使用当前目录的 Dockerfile 构建镜像。
  • mysql-db 使用现成镜像 mysql:8.0.45
  • python-app 连接 MySQL 时,主机名写 mysql-db,不是 localhost
  • 容器之间走 Docker 网络,端口用容器内部端口 3306
  • healthcheck 用来判断 MySQL 是否已经就绪。
  • depends_on.condition: service_healthy 让 Python 容器等 MySQL 健康后再启动。

这里特别容易误解的是:容器启动不等于服务就绪。 MySQL 容器起来后,还需要初始化数据目录、启动数据库进程、准备账号和库。没有健康检查时,Python 可能抢先连接,结果就是 Connection refused

接着新建 test_mysql1.py,让 Python 容器通过环境变量读取 MySQL 连接信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import os
import sys
import time

import pymysql


def get_mysql_conn():
max_retries = 10
retry_delay = 2

for i in range(max_retries):
try:
conn = pymysql.connect(
host=os.getenv("MYSQL_HOST", "localhost"),
port=int(os.getenv("MYSQL_PORT", 3306)),
user=os.getenv("MYSQL_USER", "root"),
password=os.getenv("MYSQL_PASSWORD", "123456"),
database=os.getenv("MYSQL_DB", "test_db"),
charset="utf8mb4",
connect_timeout=3,
)
print(f"第 {i + 1} 次尝试:MySQL 连接成功")
return conn
except pymysql.err.OperationalError as e:
print(f"第 {i + 1} 次尝试:MySQL 连接失败 - {e}")
if i < max_retries - 1:
time.sleep(retry_delay)
else:
raise RuntimeError("重试后仍无法连接 MySQL,请检查容器配置") from e


if __name__ == "__main__":
conn = None
cursor = None

try:
conn = get_mysql_conn()
cursor = conn.cursor()
cursor.execute("SELECT DATABASE()")
print(f"当前连接的数据库:{cursor.fetchone()[0]}")

cursor.execute(
"CREATE TABLE IF NOT EXISTS test_table "
"(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50))"
)
cursor.execute("INSERT INTO test_table (name) VALUES ('Docker Compose test')")
conn.commit()

cursor.execute("SELECT * FROM test_table")
print("test_table 中的测试数据:", cursor.fetchall())
print(f"当前 Python 版本:{sys.version}")
except Exception:
if conn:
conn.rollback()
raise
finally:
if cursor:
cursor.close()
if conn:
conn.close()

这段脚本主要看两点:

  • 连接信息来自 Compose 的 environment,而不是写死在代码里。
  • 即使已经有 healthcheck,代码层面保留短重试也更稳,因为数据库、网络、DNS 都可能短暂抖动。

3.4 运行和验证 Compose 小案例

如果前面第 2 部分已经启动过单独的 mysql-db 容器,需要先停掉并删除它,否则 9999 端口可能冲突:

1
2
3
docker ps
docker stop mysql-db
docker rm mysql-db

然后在包含 docker-compose.ymlDockerfiletest_mysql1.py 的目录下启动:

1
2
3
docker compose up -d
docker compose ps
docker compose logs python-app

如果修改了 Dockerfile 或 Python 依赖,重新构建并启动:

1
docker compose up -d --build

如果只是想重新执行一次 Python 脚本,可以临时运行:

1
docker compose run --rm python-app

想进入 MySQL 容器看数据,可以执行:

1
docker compose exec mysql-db mysql -uroot -p123456 test_db

这个案例跑通后,再看 Dify 的 Compose 文件会轻松很多。Dify 部署也是同样的思路:把一组服务按正确顺序拉起来。

3.5 Compose 配置阅读顺序

读 compose 文件不要从第一行硬啃到最后一行。可以按这个顺序看:

  1. services:这套项目启动哪些服务。
  2. image / build:服务是直接用镜像,还是本地构建。
  3. ports:哪些服务暴露给宿主机或浏览器。
  4. volumes:哪些数据、配置、模型目录会持久化或挂载。
  5. environment:服务启动依赖哪些环境变量。
  6. depends_on:服务之间的大致启动依赖。
  7. networks:哪些服务能互相访问,哪些被隔离。

常见字段速查:

字段 作用
image 使用现成镜像
build 从 Dockerfile 构建镜像
container_name 指定容器名
restart 容器异常停止后的重启策略
environment 注入环境变量
ports 宿主机端口到容器端口的映射
volumes 数据持久化或目录挂载
depends_on 启动顺序依赖
networks 容器网络隔离与互通

读 Dify 的 compose 文件时,可以先画一条线:nginx -> web/api -> db/redis/vector store。先知道请求和数据大概怎么流动,再看具体变量,会比直接盯着几百行 YAML 更轻松。

3.6 Compose 命令速查

下面命令默认在 docker-compose.yaml 所在目录执行。

目的 命令 说明
启动整套服务 docker compose up -d 后台启动
查看服务状态 docker compose ps 看 Up、healthy、Exited
查看服务日志 docker compose logs -f <服务名> 跟踪日志
临时停止服务 docker compose stop 不删除容器
启动已停止的服务 docker compose start 不重建容器
停止并删除容器、网络 docker compose down 默认不删 volume
停止并删除容器、网络、volume docker compose down -v 会删除 volume,谨慎
拉取镜像 docker compose pull 更新镜像
重新构建镜像 docker compose build <服务名> 本地 build 场景使用
查看最终配置 docker compose config 用于确认变量展开后配置
查看服务清单 docker compose config --services 只列 service 名

最容易混的是 stopdowndown -v

  • stop:只是暂停容器,容器还在。
  • down:删除容器和网络,但默认保留 volume。
  • down -v:连 volume 一起删,数据库、索引、向量库等数据可能随之消失。

日常排障优先用 pslogsrestartup -d。除非明确要清空测试环境,否则不要把 -v 当成习惯性参数。


4、数据持久化、网络、迁移与危险边界

刚学 Docker 时,最容易低估的是数据问题。容器可以删了重建,数据库、向量索引、上传文件、插件数据、配置文件和备份不行。

这一部分先讲端口和网络,再讲 volume、bind mount、迁移和清理。把顺序放在这里,是为了避免在“重装一下试试”的过程中误删数据。

4.1 端口映射

Compose 中常见配置:

1
2
ports:
- "8080:80"

左边是宿主机端口,右边是容器内端口。

也就是:

  • 浏览器访问:http://localhost:8080
  • 请求进入容器后:访问容器内部的 80 端口

再看一个 Redis Stack 示例:

1
docker run -d --name redis-stack -p 26379:6379 -p 8001:8001 redis/redis-stack

含义是:

  • 本机 26379 -> 容器内 Redis 6379
  • 本机 8001 -> 容器内 RedisInsight 8001

对 Dify 来说,浏览器访问的通常是 nginx 暴露出来的宿主机端口;而 api 访问 db_postgresredis 这类依赖时,走的是 Docker 内部网络。外部访问和容器互访不是同一条路,排查时要分开看。

4.2 volume 和 bind mount

Compose 里最常见两种写法:

1
2
3
volumes:
- mysql_data:/var/lib/mysql
- ./mysql:/docker-entrypoint-initdb.d

第一种是 命名卷 named volume

1
mysql_data:/var/lib/mysql
  • 左边 mysql_data 是 Docker 管理的持久化存储。
  • 右边 /var/lib/mysql 是容器内目录。
  • 适合保存数据库这类运行后产生的数据。

第二种是 目录挂载 bind mount

1
./mysql:/docker-entrypoint-initdb.d
  • 左边 ./mysql 是宿主机当前目录下的真实文件夹。
  • 右边 /docker-entrypoint-initdb.d 是容器内目录。
  • 适合把项目里的配置、脚本、模型文件交给容器使用。

Windows 路径写法也遵循同一个规则:冒号左边是宿主机真实目录,右边是容器内目录。容器写入右边目录时,数据会落到左边目录。

bind mount 中宿主机路径和容器路径的对应关系

粗略判断可以这样看:

  • 左边是 ./xxx../xxx/绝对路径/xxx,通常是 bind mount。
  • 左边只是 mysql_dataqdrant_data 这种名字,通常是 named volume。

Docker 官方文档也把 volume 定义为由容器引擎管理的持久化数据存储。理解这一点以后,才能解释“为什么容器删了还能恢复数据”,也能解释“为什么新版本启动后像新环境”。

4.3 容器生命周期与数据边界

容器适合随时重建,重要数据不应该只放在容器自己的临时文件系统里。

有 volume 或 bind mount 时:

1
2
3
4
5
宿主机目录或 Docker volume
|
| 挂载
v
容器内目录

程序看起来写入的是容器内目录,例如 /var/lib/postgresql/data,实际数据会落到外部 volume 或宿主机目录。

所以:

  • 删除容器:通常不删除 volume 或宿主机挂载目录。
  • 删除 volume:会删除 Docker 管理的数据。
  • 手动删除宿主机挂载目录:也会删除数据。

这也是为什么 docker compose down 通常不丢数据,而 docker compose down -v 要特别谨慎。

不过“通常不丢”有前提:关键目录确实已经挂载到 volume 或宿主机目录。如果某个服务把数据写在容器自己的可写层里,删容器就会丢。这就是为什么排障时要用 docker inspect 看 Mounts,而不是只凭感觉判断。

4.4 网络慢或镜像拉取失败

镜像拉取失败通常不是代码问题,而是 Docker 访问镜像仓库不稳定。常见处理方式有两类:

  • 在 Windows / macOS 的 Docker Desktop 里配置镜像加速或代理。
  • 在 Ubuntu 服务器的 Docker Engine 里配置 /etc/docker/daemon.json

这里容易误判的一点是:浏览器能访问外网,不代表 Docker 引擎一定能拉镜像。Docker 有自己的网络配置。

如果第 6 章安装 Dify 卡在拉镜像,优先回到本节;如果容器已经启动但页面报错,再进入第 6 部分看应用日志。这样能避免把网络问题误判成 Dify 配置问题。

如果你使用的是 Docker Desktop,通常可以在设置中找到 Docker Engine 的配置区域,然后为 registry-mirrors 增加镜像地址。Ubuntu 服务器上的配置方式见第 2.10 小节。

Docker Desktop 的 Docker Engine 配置界面与 registry-mirrors 示例

镜像源可参考 GitHub 上的汇总项目:dongyubin/DockerHub 国内镜像加速列表

镜像源可用性会随时间变化。写文档时不要把某一个镜像源当成永久可用地址,实际环境以当时可访问为准。

4.5 跨机器迁移与离线同步

Docker 学到后面,很可能会遇到迁移:本地 Windows 能跑了,要搬到 Ubuntu 服务器;有网环境能跑了,要搬到不能访问外网的内网机器。

以本章的 MySQL + Python Compose 小案例为例,迁移时别想着“把容器复制过去”。先分清三类东西:

迁移对象 作用 示例
配置文件 描述服务如何启动 docker-compose.yml.env
镜像 提供运行环境 mysql:8.0.45docker-python-mysql-python-app:latest
数据 保存业务状态 MySQL 数据目录、Docker volume、备份 SQL

4.5.1 在线迁移:目标服务器可以拉镜像

如果 Ubuntu 服务器可以访问镜像仓库,这是最简单的方式。

先在 Windows 本地项目目录停止服务:

1
docker compose down

确认项目目录里至少有这些文件:

1
2
3
docker-compose.yml
Dockerfile
test_mysql1.py

然后把整个项目目录上传到 Ubuntu,例如:

1
/home/你的用户名/docker-python-mysql

进入服务器目录后,重点修改数据卷路径。Windows 里常见写法可能是:

1
2
volumes:
- D:/mysql-data:/var/lib/mysql

到 Ubuntu 上应改成 Linux 路径,例如:

1
2
volumes:
- /home/你的用户名/mysql-data:/var/lib/mysql

然后启动:

1
2
3
4
cd /home/你的用户名/docker-python-mysql
docker compose up -d
docker compose ps
docker compose logs python-app

如果日志里能看到 Python 成功连接 MySQL,说明服务编排和容器网络已经迁移成功。

这里最容易错的是路径。Windows 的 D:/mysql-data 到 Ubuntu 上没有意义,必须改成 Linux 服务器上的真实目录。Dify 迁移也是同理,先看 volumes,再决定哪些目录或 volume 要带走。

4.5.2 离线迁移:目标服务器不能拉镜像

服务器不能访问外网、镜像拉取很慢、内网环境不允许在线下载时,可以用 docker savedocker load 迁移镜像。

先在有网机器上确认镜像:

1
docker images

使用 docker images 确认需要离线迁移的镜像

打包 MySQL 镜像和自定义 Python 镜像:

1
2
docker save mysql:8.0.45 -o ./mysql-8.0.45.tar
docker save docker-python-mysql-python-app:latest -o ./python-app.tar

把这些文件上传到目标服务器:

文件 是否必需 说明
mysql-8.0.45.tar 必需 MySQL 镜像包
python-app.tar 必需 自定义 Python 镜像包
docker-compose.yml 必需 服务编排配置
.env 有则上传 环境变量配置
数据备份 视情况 数据库 SQL 备份或挂载目录备份

在目标服务器导入镜像:

1
2
3
docker load -i ./mysql-8.0.45.tar
docker load -i ./python-app.tar
docker images

如果原来的 Compose 里写的是:

1
2
python-app:
build: .

离线环境里没有构建上下文或依赖下载条件时,可以改成已经导入的镜像:

1
2
python-app:
image: docker-python-mysql-python-app:latest

同时把数据卷路径改成 Ubuntu 路径:

1
2
3
mysql-db:
volumes:
- /home/你的用户名/mysql-data:/var/lib/mysql

最后启动并验证:

1
2
3
docker compose up -d
docker compose ps
docker compose logs python-app

离线导入镜像后使用 Compose 启动并验证服务

这个思路迁移到 Dify 也一样:提前准备好所需镜像、.envdocker-compose.yamlenvs/ 目录、volume / 挂载目录和数据库备份,再到目标服务器导入和启动。

对 Dify 这类系统,还要额外注意两点:

  • 只迁移镜像不等于迁移数据。账号、应用、工作流、知识库、上传文件都在数据库、向量库和挂载目录里。
  • 只复制 docker-compose.yaml 不等于配置一致。新旧版本的 .env.exampleenvs/*.env.example 可能新增变量,升级或迁移时要逐项对照。

4.6 危险操作清单

Docker 里有些命令看起来像“清理环境”,实际是在删数据。Dify、RAG、问数项目要格外小心:数据库或向量库没了,页面也许还能重新初始化,但旧账号、应用、工作流和知识库已经不在了。

这些命令执行前,先停几秒确认:

操作 影响 风险等级
docker compose stop 停止服务,容器仍保留
docker compose down 删除当前项目容器和网络,默认保留 volume
docker compose down -v 删除当前项目容器、网络和 volume
docker volume rm <volume> 删除指定 volume
docker volume prune 删除所有未使用 volume
rm -rf ./volumes 删除当前目录下的挂载数据
sudo rm -rf /var/lib/docker 删除 Docker 管理的全部数据 极高

本地练习时,清空数据有时只是重来一遍;服务器上就不能这么随意了。动手前先问三个问题:

  1. 当前服务是否已经停止写入?
  2. 数据库、上传文件、向量库和插件目录是否已经备份?
  3. 我删的是测试环境,还是有人正在使用的真实环境?

如果答案不确定,先执行查看命令,不要执行删除命令:

1
2
3
4
docker compose ps
docker volume ls
docker inspect <容器名> --format '{{json .Mounts}}'
ls -lah ./volumes

4.7 Docker 基础常见报错

进入 Dify 排障前,先把 Docker 自身的问题排掉。有些“平台启动失败”,实际还没到平台层。

报错或现象 常见原因 处理方式
Docker Desktop 提示 WSL 2 未配置 WSL、虚拟机平台或 WSL 内核缺失 回到第 2.2 小节检查虚拟化、WSL 功能和内核
Connection refused 目标服务没启动、端口写错、服务还没就绪 先看 docker ps,再看 docker logsdocker compose logs
Compose 提示 YAML 格式错误 缩进、冒号、引号或 Tab 问题 YAML 使用空格缩进,冒号后保留空格,避免中文引号
Ubuntu 执行 docker 提示权限不足 当前用户不在 docker 用户组 执行 sudo usermod -aG docker $USER 后重新登录
port is already allocated 宿主机端口被其他进程或容器占用 换宿主机端口,或停掉占用端口的容器
镜像拉取超时 Docker Engine 网络或镜像源不可用 配置镜像源、代理,或走离线 docker save/load

排错时按这个顺序来:

1
Docker 是否正常 -> 容器是否启动 -> 应用日志

5、把 Docker 知识迁移到 Dify 部署结构

Dify 不是一个单进程应用。用 Compose 启动后,你看到的是一组容器在一起工作。

Dify 使用 Docker Compose 启动时,通常会包含 apiworkerworker_beatwebplugin_daemon 这些服务,也会启动 weaviatedb_postgresredisnginxssrf_proxysandbox 等依赖组件。不同版本的 service 名、镜像 tag、可选组件可能变化,实际排查时以你本机的 docker-compose.yaml.envdocker compose ps 输出为准。

前面讲过的 Docker 概念,放到 Dify 里大概是这样:

Docker 概念 在 Dify 里的对应物 排障时看什么
镜像 langgenius/dify-apilanggenius/dify-webpostgresredis docker imagesdocker compose pull
容器 / 服务 apiworkerwebnginxdb_postgres docker compose psdocker ps
端口映射 EXPOSE_NGINX_PORTEXPOSE_NGINX_SSL_PORT docker compose ps.env
volume / bind mount ./volumes/db/data./volumes/app/storage docker inspect ... Mounts
网络 Compose 自动创建的项目网络、ssrf_proxy_network 服务名互访、日志里的连接失败
环境变量 .envenvs/*.env 数据库、Redis、URL、向量库、密钥配置

读 Dify 的 compose 文件,不用从第一行硬读到最后一行。先抓四类信息:

  1. 服务:这套部署启动了哪些容器。
  2. 镜像:哪些服务使用官方镜像,哪些服务可能需要本地构建。
  3. 挂载:数据库、Redis、上传文件、插件、向量库数据保存在哪里。
  4. 入口:浏览器请求从哪个服务和端口进入。

5.1 访问链路

先用一张图建立整体印象:

1
2
3
4
5
6
7
8
9
10
11
12
13
flowchart LR
U["浏览器"] --> N["nginx<br/>80 / 443"]
N --> W["web<br/>控制台页面"]
N --> A["api<br/>后端接口"]
A --> P["db_postgres<br/>账号、应用、工作流"]
A --> R["redis<br/>缓存、队列"]
A --> V["向量库<br/>知识库检索"]
A --> S["sandbox<br/>代码节点"]
A --> PD["plugin_daemon<br/>插件与工具"]
WK["worker<br/>后台任务"] --> P
WK --> R
WK --> V
WB["worker_beat<br/>定时任务"] --> WK

浏览器先访问 nginx,再由 nginx 转给 webapiapiworker 再去访问 PostgreSQL、Redis、向量库、sandbox、plugin_daemon。

排障时也按这条链路看:

  • 页面完全打不开:先看 nginx 和端口映射。
  • 页面能打开但接口报错:看 api
  • 知识库索引、异步任务不动:看 worker
  • 登录、应用、工作流数据异常:看 db_postgres
  • 队列、缓存、任务状态异常:看 redis
  • 知识库检索异常:看当前启用的向量库。

5.2 典型容器分工

层次 常见服务 主要职责
入口层 nginx 对外提供 80/443 入口,转发到 web/api
应用层 web Dify 控制台和应用页面
应用层 api 后端接口、鉴权、工作流、应用配置等
应用层 api_websocket 协作、实时通信等可选能力
任务层 worker 异步任务、知识库索引、队列消费
任务层 worker_beat 定时任务调度
数据层 db_postgres 或其他数据库服务 主业务数据库
数据层 redis 缓存、队列、任务中间状态
向量层 weaviate / milvus / opensearch 知识库向量检索,取决于配置
安全层 sandbox 安全执行代码节点
安全层 ssrf_proxy 代理外部访问,降低 SSRF 风险
插件层 plugin_daemon 插件、工具、模型供应商等扩展能力支撑
初始化 init_permissions 初始化挂载目录权限,通常执行后退出

实际环境中可以用:

1
2
3
docker compose config --services
docker compose ps
docker ps

确认当前版本到底启动了哪些服务。

读官方 compose 时,可以先按这几类服务来理解:

服务 镜像或来源 主要作用 关键挂载 / 端口
nginx nginx:latest 对外入口,转发到 web / api ${EXPOSE_NGINX_PORT:-80}${EXPOSE_NGINX_SSL_PORT:-443}
web langgenius/dify-web:<version> 前端页面 主要依赖环境变量
api langgenius/dify-api:<version> 后端接口 ./volumes/app/storage:/app/api/storage
api_websocket langgenius/dify-api:<version> WebSocket / 协作能力,可选启用 取决于 COMPOSE_PROFILES 和当前版本
worker langgenius/dify-api:<version> 后台任务、知识库索引、队列消费 ./volumes/app/storage:/app/api/storage
worker_beat langgenius/dify-api:<version> 定时任务调度 主要依赖数据库和 Redis
db_postgres postgres:15-alpine 主业务数据库 ./volumes/db/data:/var/lib/postgresql/data
db_mysql / oceanbase / seekdb 数据库镜像或外部数据库配置 可选数据库后端 取决于当前版本和 .env / envs/ 配置
redis redis:6-alpine 缓存、队列、中间状态 ./volumes/redis/data:/data
sandbox langgenius/dify-sandbox:<version> 代码节点隔离执行 ./volumes/sandbox/...
plugin_daemon langgenius/dify-plugin-daemon:<version> 插件服务 ./volumes/plugin_daemon:/app/storage
ssrf_proxy ubuntu/squid:latest 外部访问代理与 SSRF 防护 ssrf_proxy_network
weaviate / qdrant / pgvector 向量库镜像 知识库向量检索 对应 ./volumes/<向量库> 目录

读 compose 文件不是为了记住每一行,而是为了回答三个问题:启动了哪些服务,数据挂载在哪里,外部请求从哪个端口进入。

5.3 同一个镜像为什么会起多个容器

Dify 后端常见的 apiworkerworker_beat 可能使用同一个后端镜像,只是启动模式不同:

  • api:对外提供后端接口。
  • worker:消费队列并执行后台任务。
  • worker_beat:负责定时调度。

在官方 compose 里,这几个服务都可能使用 langgenius/dify-api:<version>,但通过不同的启动命令或环境变量区分角色。

示意配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
api:
image: langgenius/dify-api:<version>
environment:
MODE: api

worker:
image: langgenius/dify-api:<version>
environment:
MODE: worker

worker_beat:
image: langgenius/dify-api:<version>
environment:
MODE: beat

所以看到多个容器对应同一个镜像,不代表哪里配错了。判断职责时,要看 service 名、容器名、日志和环境变量,而不是只看镜像名。

5.4 docker 目录

Dify 仓库里的 docker/ 目录主要是部署配置,不是业务源码本身。常见内容包括:

  • docker-compose.yaml:服务清单。
  • .env:部署参数、端口、密钥、数据库连接等。
  • envs/:按核心服务、数据库、向量库、安全配置等拆分的环境变量文件。
  • nginx/:入口代理配置模板。
  • volumes/:部分部署方式下的运行数据和挂载目录。

官方 docker-compose.yaml 开头也明确提示:该文件由生成脚本生成,不建议直接手动大改。日常部署优先调整 .env;确实需要改 compose 行为时,再考虑 override 或模板层面的改造。

如果 Compose 里使用的是 image: langgenius/dify-web:<version>image: langgenius/dify-api:<version>,实际运行的是官方已经构建好的镜像,不会自动读取你本地的 web/api/ 源码目录。

只有某个服务配置了 build:,例如:

1
2
3
4
services:
web:
build:
context: ../web

才表示 Compose 会从本地源码构建镜像。

因此,docker/ 目录更像部署说明书,不是源码目录。后面第 7.2 小节讲“改前端不生效”,原因也在这里:你改了本地源码,但容器可能仍在运行官方镜像。

5.5 数据存储位置

看到这里,重点已经从“容器怎么启动”变成了“数据在哪里”。Dify 的容器可以重建,数据库、向量库、上传文件不能随便丢。

确认 Dify 数据位置时,优先看 Compose 中的挂载配置,而不是直接进容器翻文件。

Dify 的关键数据通常包括:

数据类型 常见服务 常见位置
账号、应用、工作流、配置 PostgreSQL ./volumes/db/data 或 named volume
缓存、队列状态 Redis ./volumes/redis/data 或 named volume
知识库向量数据 Weaviate / Milvus / OpenSearch 等 ./volumes/weaviate 或对应 volume
用户上传文件、应用运行文件 API / Worker ./volumes/app/storage
插件数据 plugin_daemon ./volumes/plugin_daemon
代码节点依赖和配置 sandbox ./volumes/sandbox/dependencies./volumes/sandbox/conf
Nginx 运行配置或证书 nginx ./nginx./volumes/nginx
HTTPS 证书申请数据 certbot ./volumes/certbot/...

具体路径以当前版本的 docker-compose.yaml 为准。要确认一个容器实际挂了什么目录,可以执行:

1
docker inspect <容器名> --format '{{json .Mounts}}'

例如查看 PostgreSQL:

1
docker inspect docker-db_postgres-1 --format '{{json .Mounts}}'

如果容器名不一致,先用 docker ps 看 NAMES 列,再替换命令里的容器名。

判断数据位置时,优先相信 docker-compose.yamldocker inspect,不要只看文件夹名字。有些版本使用 ./volumes/...,有些环境使用 Docker named volume,路径表现会不同。

5.6 删除容器会不会丢数据

通常不会。前提是数据已经通过 volume 或宿主机目录挂载出来。

危险操作主要是这些:

1
2
3
4
docker compose down -v
docker volume rm <volume名>
docker volume prune
rm -rf ./volumes

这些操作可能删除真实数据。升级、迁移和清理环境前,先确认 volume 和挂载目录,再备份。

本地测试环境里,清空 volume 有时是为了重新来一遍;服务器或生产环境里,它通常就是高风险操作。看到 -vvolume rmprunerm -rf,都应该先停一下确认。

docker compose down 默认不删数据,但升级过程可能执行数据库 migration,改表结构或写入新数据。升级中途失败时,volume 可能还在,但里面的数据已经处于半升级状态。

所以升级前必须做逻辑备份,例如用 pg_dump 导出 PostgreSQL 数据。备份文件和 volume 不是一回事:

  • Volume:数据库原始数据目录。
  • pg_dump:可恢复、可迁移的 SQL 备份。

建议保留 volume,但不要只依赖 volume;先导出 SQL 备份,再启动新版本。这样即使 migration 失败,也还有一条可恢复路径。


6、Dify 排障:状态、日志、数据与数据库连接

Dify 排障别一上来就改 .env,也别一上来就重装。先判断问题发生在哪一层,再看对应日志。

现象 优先检查 常用命令
浏览器完全打不开 Docker 是否运行、nginx 是否启动、端口是否映射 docker compose psdocker compose logs -f nginx
页面能打开但接口报错 api 日志、数据库和 Redis 连接 docker compose logs -f api
知识库索引卡住 worker、向量库、Redis docker compose logs -f worker
升级后进入初始化页 PostgreSQL 是否读到旧数据、volume 是否变了 psql 查询、docker inspect ... Mounts
数据库工具连不上 端口映射、SSH 隧道、数据库账号密码 docker psssh -L ...

6.1 常见排障路径

遇到 Dify 无法访问时,建议按下面的链路排查:

1
2
3
4
5
6
7
flowchart TD
A["Docker 是否运行"] --> B["是否在正确 docker 目录执行命令"]
B --> C["docker compose ps 看容器状态"]
C --> D["入口端口是否映射"]
D --> E["nginx / web / api 日志"]
E --> F["db / redis / 向量库日志"]
F --> G[".env 配置和数据挂载"]

这个顺序是为了把问题分层定位:先排 Docker 和目录问题,再看容器、网络、日志,最后再进入 Dify 配置和数据。

6.2 页面无法访问

先执行:

1
2
3
4
5
docker compose ps
docker ps
docker compose logs -f nginx
docker compose logs -f web
docker compose logs -f api

重点看:

  • nginx 是否运行。
  • webapi 是否运行。
  • nginx 是否映射了你访问的端口,例如 80:808080:80
  • .env 中端口、域名、URL 配置是否和访问地址一致。
  • 本机防火墙、代理、端口占用是否影响访问。

如果 nginx 没起来,先看入口层;如果 nginx 正常但接口报错,继续看 api;如果 api 连不上依赖,再看数据库、Redis、向量库。.env 建议在日志已经指向配置问题时再修改。

6.3 容器启动失败或重启循环

先找具体失败服务:

1
docker compose ps

再看日志:

1
docker compose logs -f <服务名>

例如:

1
2
3
4
docker compose logs -f api
docker compose logs -f worker
docker compose logs -f db_postgres
docker compose logs -f redis

常见原因:

  • .env 缺少变量或变量不兼容当前版本。
  • 数据库、Redis、向量库还没准备好。
  • 端口被占用。
  • 镜像没拉完整。
  • 旧版本数据和新版本 migration 不兼容。

日志里先找第一条关键错误,不要只看最后一行。有些容器会因为前面的配置或连接错误反复重启,最后一行只是“进程退出”。

6.4 升级后进入初始化页面

如果升级后页面进入初始化安装页,通常说明新环境没有读到旧数据库。

这类页面不是“正常升级完成”,而是新环境像第一次安装一样重新进入了初始化流程:

Dify 升级后进入初始化管理员页面的示例

也可能在浏览器里看到安装/初始化入口。出现这种情况时,不要急着重新初始化,先检查旧数据库是否还在:

Dify 升级后进入安装入口页面的示例

先查 PostgreSQL 里是否有用户表数据:

1
docker exec -it docker-db_postgres-1 psql -U postgres -d dify -c "select count(*) as users_count from account;"

通过 psql 查询 Dify 用户表记录数以判断是否读到旧数据库

如果用户表有记录,说明数据库里仍有旧数据;如果为 0 或表不存在,需要继续查库名和挂载。

如果容器名或数据库名不同,以你的 docker ps\l 输出为准。

继续查当前数据库容器挂载了哪个 volume:

1
2
docker inspect docker-db_postgres-1 --format '{{json .Mounts}}'
docker volume ls

docker volume ls 可以帮助确认当前环境里有哪些 Docker 管理的数据卷:

使用 docker volume ls 查看 Dify 相关 volume 列表

docker inspect 的 Mounts 字段能看到数据库容器实际挂载到了哪里:

使用 docker inspect 查看 PostgreSQL 容器的 Mounts 挂载信息

常见原因:

  • 新版本 Compose 创建了新的 volume。
  • .env 中数据库 service 名或 profile 没同步。
  • 旧数据还在旧目录或旧 volume,但新容器没有挂载到那里。
  • 需要从升级前备份的 SQL 文件导入。

这一类问题通常不在页面,而在于“新容器没有读到旧数据库”。排查时重点看数据库库名、volume 名、挂载路径和 .env 的数据库配置。

6.5 用 Navicat 连接 Dify 数据库

不要直接修改数据库文件目录。docker/volumes/db/data 这类目录是 PostgreSQL 的底层数据文件,不是给 Navicat 直接打开的。

要管理数据库,应该通过 PostgreSQL 协议连接。

本节只讨论如何通过 PostgreSQL 协议安全连接数据库。生产环境中,改表、删数据、批量更新前都应先备份,并确认影响范围。

本地开发环境:暴露端口

如果只是本地学习,可以在 Compose 中给 PostgreSQL 暴露端口,例如:

1
2
3
4
services:
db_postgres:
ports:
- "5432:5432"

然后重启数据库服务:

1
docker compose up -d db_postgres

Navicat 连接参数通常类似:

参数 示例
Host 127.0.0.1
Port 5432
User .env 中的数据库用户
Password .env 中的数据库密码
Database dify

注意:生产环境不要把 5432 直接暴露到公网。

本地学习时暴露 5432:5432 没问题,因为访问范围通常只在你自己的电脑上。服务器环境要多考虑安全组、防火墙、数据库弱口令和公网扫描。

服务器环境:优先用 SSH 隧道

服务器环境里,更推荐让数据库只在服务器内部访问,然后用 SSH 隧道把本机端口转发过去。

思路是:

1
2
3
4
Navicat 本机端口
-> SSH 隧道
-> 服务器
-> PostgreSQL 容器或宿主机映射端口

示例:

1
ssh -L 15432:127.0.0.1:5432 user@your-server

然后 Navicat 连:

参数 示例
Host 127.0.0.1
Port 15432
User .env 中的数据库用户
Password .env 中的数据库密码
Database dify

如果数据库端口没有映射到宿主机,而只在容器网络里可见,需要先确认容器 IP 或临时使用跳板方式。生产环境建议由运维统一配置安全访问方式。

有些团队会用堡垒机、VPN、内网跳板或云厂商数据库访问策略来做这件事。课程里给的是通用思路,实际生产环境要服从团队的安全规范。

Mac + SSH 隧道连接服务器 PostgreSQL

下面是一条更贴近实操的路径:本机用 Navicat,数据库仍留在服务器 Docker 内部网络里,不把 5432 暴露到公网。

先在服务器上确认 Postgres 容器 IP:

1
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' docker-db_postgres-1

假设输出为 172.20.0.4,后面 SSH 隧道就转发到这个容器 IP。

在 Mac 本地保存服务器私钥,例如:

在 Mac 本地保存用于 SSH 隧道连接的服务器私钥

1
2
nano ~/.ssh/aliyun_navicat.pem
chmod 600 ~/.ssh/aliyun_navicat.pem

然后建立隧道:

1
2
3
4
ssh -i ~/.ssh/aliyun_navicat.pem \
-N \
-L 15432:172.20.0.4:5432 \
root@your-server-ip

这个终端窗口需要保持打开。关闭窗口,隧道就断开。

建立 SSH 隧道后终端保持连接的状态

Navicat 里不要再勾选 SSH 选项,因为隧道已经由命令行建好了。它只需要连接本机端口:

Navicat 中通过本机 15432 端口连接 PostgreSQL 的配置

常见填写方式:

参数 示例
Host 127.0.0.1
Port 15432
User .env 中的数据库用户
Password .env 中的数据库密码
Database dify

连接成功后,就可以像普通 PostgreSQL 一样查看 Dify 表结构和数据:

Navicat 成功连接 Dify PostgreSQL 数据库后的表结构视图

生产环境里,查看表结构通常没问题;如果要改数据、删数据或批量更新,先备份,再确认影响范围。


7、Dify 升级、备份与源码改造

Dify 升级不是简单执行 docker compose pull && docker compose up -d。它同时牵涉镜像、.env、数据库 migration、volume 挂载、插件目录、向量库和前后端版本兼容。

升级前先按这个清单确认一遍:

检查项 为什么重要 推荐动作
旧环境是否健康 带着旧问题升级,会让排障更乱 先看 docker compose psapi 日志
数据库是否已备份 migration 可能改表结构 pg_dump 导出 SQL
volume / 挂载目录是否确认 新版本可能挂到新目录 docker inspect 看 Mounts
.env 是否对照新版本 新版本可能新增或废弃变量 对比 .env.exampleenvs/*.env.example
是否需要回滚 升级失败时要能退回 保留旧目录、旧 .env、备份文件

7.1 升级流程

升级前的原则:先备份,后停服务;先确认数据位置,再启动新版本。

下面用“旧版本 -> 新版本”的通用流程说明。命令里的容器名、路径和版本号都要按你的环境替换。

Dify 升级不仅是拉取新镜像,还涉及镜像版本、.env 配置、数据库 migration、volume 挂载和前后端兼容。任何一个环节没对上,都可能表现为页面打不开、进入 /install 或后台任务异常。

第 1 步:升级前检查

在旧版本 docker 目录执行:

1
2
3
docker compose ps
docker ps
docker compose logs --tail=100 api

先确认旧环境本身是可用的。不要在旧环境已经异常、数据状态不明时直接升级。

如果旧环境已经有报错,先记录当前状态和日志。否则升级失败后,很难判断问题是升级引入的,还是旧环境本来就已经异常。

第 2 步:确认数据库名

1
docker exec -it docker-db_postgres-1 psql -U postgres -c "\l"

常见业务库名是 dify。如果你的数据库容器叫 docker-db-1 或其他名字,按 docker ps 结果替换。

第 3 步:备份数据库

1
docker exec -t docker-db_postgres-1 pg_dump -U postgres dify > dify_backup.sql

建议把备份文件复制到安全位置,不要只放在即将改动的部署目录里。

备份完成后,最好至少确认文件不是 0 字节;重要环境还可以在测试库里试导入一次。没有验证过的备份,只能算“可能有用”。

第 4 步:停旧版本

1
docker compose down

这一步会删除当前 Compose 项目创建的容器和网络,但默认不删除 volume。除非你明确要清空数据,否则不要使用 docker compose down -v

这里用 down 是为了让旧容器退出,避免新旧版本同时占用端口或写同一份数据。它不是清库操作,也不应该顺手加 -v

执行后,终端里通常会看到容器和网络被移除:

执行 docker compose down 后容器和网络被移除的终端输出

可以再看一眼 volume 是否仍然存在:

docker compose down 后使用 docker volume ls 确认 volume 仍然存在

第 5 步:准备新版本

常见方式是拉取或解压新版本 Dify,然后进入新版本的 docker 目录:

1
cd /opt/dify/dify-<new-version>/docker

如果使用 git clone 拉取新版本,网络不稳定时可能会中断,失败后可以重试或先处理代理/镜像源问题:

通过 git clone 拉取 Dify 新版本源码目录的终端示例

把旧版本 .env 复制过来,再按新版本 .env.example 补齐新增变量。若项目提供同步脚本,可以使用官方推荐脚本;如果没有,就逐项对照新旧 .env

同步脚本或配置对比工具通常会提示哪些变量需要保留、补齐或调整:

Dify 升级时同步 .env 配置后的终端输出示例

实际改配置时,建议一项一项确认,不要整段盲贴:

在终端编辑 Dify .env 配置文件的示例

重点检查:

  • 数据库服务名,例如 DB_HOST
  • COMPOSE_PROFILES
  • 端口和域名。
  • 密钥类配置。
  • 向量库类型和对应连接配置。

.env 是升级里最容易被忽略的文件。旧版本能跑,不代表旧 .env 能完整满足新版本。新增变量可以用默认值起步,但数据库、密钥、URL、向量库类型这几类不能随便变。

改完后用 grep 之类的命令确认关键变量已经生效:

使用 grep 确认 Dify 升级后的关键 .env 变量

第 6 步:拉取并启动新版本

1
2
docker compose pull
docker compose up -d

镜像拉取阶段会逐个下载服务镜像:

执行 docker compose pull 拉取 Dify 服务镜像的输出示例

启动后可以用 docker psdocker compose ps 查看容器状态:

Dify 新版本启动后使用 docker ps 查看容器状态

启动后查看:

1
2
3
docker compose ps
docker compose logs -f api
docker compose logs -f worker

如果进入 /install 或没有旧数据,回到第 6.4 小节检查 volume 和数据库数据。

启动后先不要急着清理旧目录和旧备份。等登录、应用列表、知识库、工作流、文件上传、后台任务都确认正常后,再考虑归档旧版本。

第 7 步:必要时导入旧备份

如果确认新环境是空库,且你已经决定用备份恢复,可以导入:

1
cat /path/to/dify_backup.sql | docker exec -i docker-db_postgres-1 psql -U postgres -d dify

如果新库已经初始化过,直接导入可能出现重复索引、重复约束等错误。更稳妥的做法是:确认没有新业务数据后,重建空库再导入。

这里的“确认没有新业务数据”很重要。如果新环境已经有人注册、创建应用或上传文件,直接重建数据库会覆盖这些新数据。生产环境恢复前应先停服务并保留当前状态备份。

如果看到大量 already exists 之类的错误,通常说明目标库不是干净空库:

向已初始化数据库导入备份时出现 already exists 重复对象报错

确认可以清空目标库后,再执行:

1
2
3
4
docker exec -it docker-db_postgres-1 psql -U postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='dify' AND pid <> pg_backend_pid();"
docker exec -it docker-db_postgres-1 psql -U postgres -c "DROP DATABASE IF EXISTS dify;"
docker exec -it docker-db_postgres-1 psql -U postgres -c "CREATE DATABASE dify OWNER postgres;"
cat /path/to/dify_backup.sql | docker exec -i docker-db_postgres-1 psql -U postgres -d dify

重建空库并重新导入时,先确认你手里已经有可用备份:

重建 dify 数据库并准备重新导入备份的终端示例

完成后重启关键服务:

1
docker restart docker-api-1 docker-worker-1 docker-worker_beat-1 docker-web-1

7.2 修改 Dify 前端源码并让改动生效

如果 Compose 使用的是官方镜像:

1
2
3
services:
web:
image: langgenius/dify-web:<version>

那么你修改本地 web/ 目录不会自动生效。因为实际运行的是镜像里已经构建好的前端产物。

要让本地源码生效,需要改为构建自己的 web 镜像。

这一节解决的是开发改造问题,不是普通部署问题。如果只是安装或升级 Dify,不需要改这里;只有你真的要改前端代码、换交互、加页面,才需要走本地 build。

推荐用 override

docker 目录新增 docker-compose.override.yaml

1
2
3
4
5
6
services:
web:
build:
context: ../web
dockerfile: Dockerfile
image: my-dify-web:local

这样升级时更容易对比官方文件,不会把本地改动混进官方 docker-compose.yaml

官方 compose 文件本身是生成文件,升级时也最容易变化。override 的好处是把“官方部署文件”和“本地改造”分开。以后升级 Dify 时,可以先更新官方 compose,再单独检查自己的 override 是否还适配。

构建并重启 web

docker 目录执行:

1
2
docker compose build web
docker compose up -d web

如果前端依赖环境变量或构建参数,还要按当前 Dify 版本的 web/Dockerfile 和官方说明补齐。

源码构建场景下是否需要 pull

  • 改前端源码时:web 使用本地 build,不靠 docker compose pull 获取官方 web 镜像。
  • 其他服务:apipostgresredisweaviate 等仍可按升级流程拉取官方镜像。

如果同时改了后端 api/,也要为 api 服务配置对应的 build 和镜像;只改 web,不会让后端代码变化生效。


8、Docker 常用命令速查

8.1 镜像管理

目的 命令
查看本地镜像 docker images
拉取镜像 docker pull mysql:8.0.45
搜索镜像 docker search mysql
删除镜像 docker rmi mysql:8.0.45
查看镜像详细信息 docker inspect mysql:8.0.45
构建自定义镜像 docker build -t my-app:1.0 .
打包镜像 docker save mysql:8.0.45 -o mysql.tar
导入镜像 docker load -i mysql.tar

8.2 容器管理

目的 命令
查看运行中容器 docker ps
查看全部容器 docker ps -a
查看容器日志 docker logs -f <容器名>
进入容器 docker exec -it <容器名> sh
停止容器 docker stop <容器名>
启动容器 docker start <容器名>
重启容器 docker restart <容器名>
删除已停止容器 docker rm <容器名>
查看容器挂载 docker inspect <容器名> --format '{{json .Mounts}}'

8.3 Compose 项目管理

目的 命令
启动整套服务 docker compose up -d
启动并重新构建 docker compose up -d --build
查看服务状态 docker compose ps
查看全部服务容器 docker compose ps -a
查看服务清单 docker compose config --services
查看某个服务日志 docker compose logs -f <服务名>
临时运行一个服务 docker compose run --rm <服务名>
进入服务容器执行命令 docker compose exec <服务名> sh
拉取镜像 docker compose pull
构建服务镜像 docker compose build <服务名>
停止服务 docker compose stop
停止并删除容器、网络 docker compose down
停止并删除容器、网络、volume docker compose down -v
停止并删除项目关联镜像 docker compose down --rmi all
查看最终配置 docker compose config

8.4 Dify 常用命令

下面命令默认在 Dify 的 docker 目录执行。

目的 命令
查看服务清单 docker compose config --services
查看全部容器 docker ps -a
查看 API 日志 docker compose logs -f api
查看 worker 日志 docker compose logs -f worker
查看 nginx 日志 docker compose logs -f nginx
查看数据库日志 docker compose logs -f db_postgres
进入数据库容器 docker exec -it docker-db_postgres-1 sh
连接 PostgreSQL docker exec -it docker-db_postgres-1 psql -U postgres -d dify
查看容器挂载 docker inspect <容器名> --format '{{json .Mounts}}'
查看 volume docker volume ls
备份数据库 docker exec -t docker-db_postgres-1 pg_dump -U postgres dify > dify_backup.sql

容器名和服务名可能因 Dify 版本或 Compose 项目名不同而变化。命令执行失败时,先用 docker compose psdocker ps 确认当前环境里的真实名称。

8.5 Ubuntu 服务器常用命令

目的 命令
查看 Docker 服务状态 sudo systemctl status docker
重启 Docker 服务 sudo systemctl restart docker
配置当前用户免 sudo 使用 sudo usermod -aG docker $USER
查看 Docker Engine 配置 docker info
编辑镜像源配置 sudo vim /etc/docker/daemon.json
重新加载 systemd 配置 sudo systemctl daemon-reload
停止 Docker 服务 sudo systemctl stop docker
卸载 Docker 软件包 sudo apt remove -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
删除 Docker 数据目录 sudo rm -rf /var/lib/docker /var/lib/containerd

最后一条是高风险清理命令,会删除本机 Docker 管理的数据。只在确认没有重要容器、镜像、volume 后使用。

8.6 危险操作清单

这些命令不是不能用,而是不能在没备份、没确认环境的情况下随手用:

命令 可能后果 使用前必须确认
docker compose down -v 删除当前 Compose 项目的 volume 数据库、向量库、上传文件是否已备份
docker volume rm <volume名> 删除指定 volume 这个 volume 是否真的是废弃数据
docker volume prune 删除所有未被容器使用的 volume 是否有停掉但还要保留数据的项目
docker system prune -af 清理未使用容器、网络、镜像、构建缓存 是否能接受重新拉镜像和重建缓存
rm -rf ./volumes 删除当前部署目录下的挂载数据 是否已经确认是测试环境或有备份
sudo rm -rf /var/lib/docker 删除 Docker Engine 管理的全部本地数据 是否是在重装机器,且已迁出所有数据
DROP DATABASE dify; 删除 Dify 业务数据库 是否已经停服务、备份、确认目标库可清空

确实要清空测试环境时,建议先把“查看命令”和“删除命令”分开执行:

1
2
3
docker compose ps
docker volume ls
docker inspect <容器名> --format '{{json .Mounts}}'

确认之后,再执行清理命令。这个习惯在本地看起来慢一点,到了服务器上能少很多麻烦。

本章小结:

  • 镜像是模板,容器是运行实例。
  • Dockerfile 负责构建镜像,Compose 负责编排多服务。
  • ports 左边是宿主机端口,右边是容器端口。
  • volumes 决定数据和文件从哪里来、落到哪里去。
  • Windows 本地和 Ubuntu 服务器的 Docker 使用方式相通,但路径、权限、镜像源配置不同。
  • Dify 的 Docker 部署是一组服务协同,不是单个容器。
  • 排障时先看容器状态和日志,再看端口、.env、数据库和向量库。
  • 升级前先备份数据库,避免 migration 或误操作带来不可逆损失。