4、数据持久化、网络、迁移与危险边界
刚学 Docker 时,最容易低估的是数据问题。容器可以删了重建,数据库、向量索引、上传文件、插件数据、配置文件和备份不行。
这一部分先讲端口和网络,再讲 volume、bind mount、迁移和清理。把顺序放在这里,是为了避免在“重装一下试试”的过程中误删数据。
4.1 端口映射
Compose 中常见配置:
ports:
- "8080:80"左边是宿主机端口,右边是容器内端口。
也就是:
- 浏览器访问:
http://localhost:8080 - 请求进入容器后:访问容器内部的
80端口
再看一个 Redis Stack 示例:
docker run -d --name redis-stack -p 26379:6379 -p 8001:8001 redis/redis-stack含义是:
- 本机
26379-> 容器内 Redis6379 - 本机
8001-> 容器内 RedisInsight8001
对 Dify 来说,浏览器访问的通常是 nginx 暴露出来的宿主机端口;而 api 访问 db_postgres、redis 这类依赖时,走的是 Docker 内部网络。外部访问和容器互访不是同一条路,排查时要分开看。
4.2 volume 和 bind mount
Compose 里最常见两种写法:
volumes:
- mysql_data:/var/lib/mysql
- ./mysql:/docker-entrypoint-initdb.d第一种是 命名卷 named volume。
mysql_data:/var/lib/mysql- 左边
mysql_data是 Docker 管理的持久化存储。 - 右边
/var/lib/mysql是容器内目录。 - 适合保存数据库这类运行后产生的数据。
第二种是 目录挂载 bind mount。
./mysql:/docker-entrypoint-initdb.d- 左边
./mysql是宿主机当前目录下的真实文件夹。 - 右边
/docker-entrypoint-initdb.d是容器内目录。 - 适合把项目里的配置、脚本、模型文件交给容器使用。
Windows 路径写法也遵循同一个规则:冒号左边是宿主机真实目录,右边是容器内目录。容器写入右边目录时,数据会落到左边目录。

粗略判断可以这样看:
- 左边是
./xxx、../xxx、/绝对路径/xxx,通常是 bind mount。 - 左边只是
mysql_data、qdrant_data这种名字,通常是 named volume。
Docker 官方文档也把 volume 定义为由容器引擎管理的持久化数据存储。理解这一点以后,才能解释“为什么容器删了还能恢复数据”,也能解释“为什么新版本启动后像新环境”。
4.3 容器生命周期与数据边界
容器适合随时重建,重要数据不应该只放在容器自己的临时文件系统里。
有 volume 或 bind mount 时:
宿主机目录或 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 小节。

镜像源可参考 GitHub 上的汇总项目:dongyubin/DockerHub 国内镜像加速列表。
镜像源可用性会随时间变化。写文档时不要把某一个镜像源当成永久可用地址,实际环境以当时可访问为准。
4.5 跨机器迁移与离线同步
Docker 学到后面,很可能会遇到迁移:本地 Windows 能跑了,要搬到 Ubuntu 服务器;有网环境能跑了,要搬到不能访问外网的内网机器。
以本章的 MySQL + Python Compose 小案例为例,迁移时别想着“把容器复制过去”。先分清三类东西:
| 迁移对象 | 作用 | 示例 |
|---|---|---|
| 配置文件 | 描述服务如何启动 | docker-compose.yml、.env |
| 镜像 | 提供运行环境 | mysql:8.0.45、docker-python-mysql-python-app:latest |
| 数据 | 保存业务状态 | MySQL 数据目录、Docker volume、备份 SQL |
4.5.1 在线迁移:目标服务器可以拉镜像
如果 Ubuntu 服务器可以访问镜像仓库,这是最简单的方式。
先在 Windows 本地项目目录停止服务:
docker compose down确认项目目录里至少有这些文件:
docker-compose.yml
Dockerfile
test_mysql1.py然后把整个项目目录上传到 Ubuntu,例如:
/home/你的用户名/docker-python-mysql进入服务器目录后,重点修改数据卷路径。Windows 里常见写法可能是:
volumes:
- D:/mysql-data:/var/lib/mysql到 Ubuntu 上应改成 Linux 路径,例如:
volumes:
- /home/你的用户名/mysql-data:/var/lib/mysql然后启动:
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 save 和 docker load 迁移镜像。
先在有网机器上确认镜像:
docker images
打包 MySQL 镜像和自定义 Python 镜像:
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 备份或挂载目录备份 |
在目标服务器导入镜像:
docker load -i ./mysql-8.0.45.tar
docker load -i ./python-app.tar
docker images如果原来的 Compose 里写的是:
python-app:
build: .离线环境里没有构建上下文或依赖下载条件时,可以改成已经导入的镜像:
python-app:
image: docker-python-mysql-python-app:latest同时把数据卷路径改成 Ubuntu 路径:
mysql-db:
volumes:
- /home/你的用户名/mysql-data:/var/lib/mysql最后启动并验证:
docker compose up -d
docker compose ps
docker compose logs python-app
这个思路迁移到 Dify 也一样:提前准备好所需镜像、.env、docker-compose.yaml、envs/ 目录、volume / 挂载目录和数据库备份,再到目标服务器导入和启动。
对 Dify 这类系统,还要额外注意两点:
- 只迁移镜像不等于迁移数据。账号、应用、工作流、知识库、上传文件都在数据库、向量库和挂载目录里。
- 只复制
docker-compose.yaml不等于配置一致。新旧版本的.env.example、envs/*.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 管理的全部数据 | 极高 |
本地练习时,清空数据有时只是重来一遍;服务器上就不能这么随意了。动手前先问三个问题:
- 当前服务是否已经停止写入?
- 数据库、上传文件、向量库和插件目录是否已经备份?
- 我删的是测试环境,还是有人正在使用的真实环境?
如果答案不确定,先执行查看命令,不要执行删除命令:
docker compose ps
docker volume ls
docker inspect <容器名> --format '{{json .Mounts}}'
ls -lah ./volumes4.7 Docker 基础常见报错
进入 Dify 排障前,先把 Docker 自身的问题排掉。有些“平台启动失败”,实际还没到平台层。
| 报错或现象 | 常见原因 | 处理方式 |
|---|---|---|
| Docker Desktop 提示 WSL 2 未配置 | WSL、虚拟机平台或 WSL 内核缺失 | 回到第 2.2 小节检查虚拟化、WSL 功能和内核 |
Connection refused | 目标服务没启动、端口写错、服务还没就绪 | 先看 docker ps,再看 docker logs 或 docker compose logs |
| Compose 提示 YAML 格式错误 | 缩进、冒号、引号或 Tab 问题 | YAML 使用空格缩进,冒号后保留空格,避免中文引号 |
Ubuntu 执行 docker 提示权限不足 | 当前用户不在 docker 用户组 | 执行 sudo usermod -aG docker $USER 后重新登录 |
port is already allocated | 宿主机端口被其他进程或容器占用 | 换宿主机端口,或停掉占用端口的容器 |
| 镜像拉取超时 | Docker Engine 网络或镜像源不可用 | 配置镜像源、代理,或走离线 docker save/load |
排错时按这个顺序来:
Docker 是否正常 -> 容器是否启动 -> 应用日志