Docker Compose 更新完,网站突然回到安装页面,账号、文章或上传文件全不见了?先别重新初始化。常见原因是新容器挂到了另一块空卷,旧数据可能仍在 VPS 上。
这篇按“确认连接对象 → 找实际挂载 → 核对旧卷 → 备份后接回”的顺序排查,适用于单机 Docker Engine。它能定位挂错卷,不能保证恢复已删除的卷或损坏的数据库。下面的名称都是示例,操作前换成真实项目、服务和卷名。
出现空站后,暂停用户上传、注册和后台任务,保留新旧实例及配置。不确定服务归属时先调查,再安排停机;别用删除容器来“试试看”。暂时不要执行 docker compose down -v、docker volume rm 或任何卷清理命令。
Compose down 文档说明:普通 down 默认删除容器和网络,具名卷会保留;加 -v 则会删除配置声明的具名卷及关联匿名卷,external 卷除外。匿名卷即使保留,后续 up 也不会自动接回。
| 现象 | 优先核对 | 可能的处理方向 |
|---|---|---|
| 换目录后变成全新安装 | Compose 项目名与卷名 | 找回旧项目对应的卷 |
| 文件目录突然为空 | bind mount 的实际 Source | 接回原来的绝对路径 |
| 升级后出现新数据库 | 数据目录、镜像版本、连接地址 | 核对版本文档与原数据库 |
| 已执行删除卷命令 | 卷列表、备份和云盘快照 | 从备份恢复或停止写盘求助 |
在原部署目录检查,命令中的 -p、-f、--env-file 必须与部署脚本或面板配置一致。下面用 shop 作为示例项目名:
docker context show
docker info --format '{{.Name}}'
docker compose ls -a
docker compose -p shop -f compose.yaml ps -a
docker compose -p shop -f compose.yaml config
本地电脑、远程 Docker context、rootless Docker 和系统 Docker daemon 可能是不同的数据世界。卷列表为空之前,先确认连接对象。config 会展开环境变量,检查时注意数据库地址和库名,不要把含密码的输出直接贴到公开论坛。
如果应用连到了另一台数据库,卷挂载正确也会显示空数据。配置来源的排查可接着看 Compose 修改环境变量不生效怎么办。
从 ps -a 或面板复制真实容器名,检查 .Mounts:
docker inspect shop-app-1 --format '{{json .Mounts}}'
docker volume ls
docker volume inspect shop_data
docker ps -a --filter volume=shop_data
重点看四项:Type 是 volume 还是 bind;Name 是实际卷名;Source 是来源;Destination 是容器内路径。将它们与旧容器、旧配置和镜像要求的数据目录逐项对照。卷创建时间和 Compose 标签可以提供线索,不能单靠名字相似就认定数据归属。
如果业务数据只写在容器可写层,没有挂到持久存储,重建移除旧容器时这层数据也会消失。若旧容器还在,先保留它,安排一致性备份后提取;不要启动多个数据库实例共用同一数据目录。
项目名规则中,-p 优先于 COMPOSE_PROJECT_NAME,再到顶层 name,最后才是目录推导的默认值。所以不能只看当前目录名。
下面是独立的配置演示,不是生产应用恢复文件。BusyBox 只用于占位,/data 是演示目录:
services:
app:
image: busybox:1.37
command: ["sleep", "86400"]
volumes:
- data:/data
volumes:
data: {}
用项目名 shop 解析时,默认卷名为 shop_data;改成 shopnew 后变成 shopnew_data。配置中都写 data,实际引用却不同,新的空卷可能触发应用初始化。
找回旧卷后,可以在原生产配置中保留原镜像、环境变量和容器内数据路径,只把对应卷声明改为:
volumes:
data:
external: true
name: shop_data
external 与 name 的说明明确:name 使用真实卷名,不再加项目名前缀;external: true 要求卷已经存在,启动时找不到会报错,而不是创建空卷。它不验证卷内容,也不是备份机制。
在变更前完成可恢复备份,停止所有会写这块卷的旧实例,再核对最终 config,按维护窗口启动原应用。数据库应使用与旧数据兼容的版本;不要趁恢复顺便升级大版本,也不要把上面的 BusyBox 示例替换进生产服务。
把项目从 /opt/shop 复制到 /opt/shop-new,相对路径 ./data 可能指向另一个目录。多文件部署还要核对基础 Compose 文件所在目录,最终以容器 inspect 的 Source 为准。
bind mount 文档指出,挂载会遮住容器目标路径原有内容;部分短语法在来源不存在时还会自动创建空目录。看到空目录不等于旧目录已删除。
下面是另一个独立演示配置。绝对路径便于核对,create_host_path: false 用于让来源缺失时启动失败:
services:
app:
image: busybox:1.37
command: ["sleep", "86400"]
volumes:
- type: bind
source: /srv/shop-data
target: /data
bind:
create_host_path: false
先检查旧路径是否存在、文件日期和容量是否符合预期。不要把空目录覆盖回旧目录,也别批量 chmod 777。若升级涉及容器数据路径变化,应查对应镜像的迁移说明;例如 PostgreSQL 18 部署教程单独说明了其卷路径变化。
接回前确认:这确实是原业务数据;没有其他实例并发写入;镜像版本与数据格式兼容;备份能在独立环境恢复。运行中的数据库不能靠随手打包数据目录保证一致性,按数据库的原生备份或停机备份流程处理,参考 VPS 备份恢复演练。
恢复后分别检查旧账号、几条历史记录、上传文件和后台任务,再做一次新写入与读取验证。旧站能打开仍不够:如果空站已经接收了新订单或文件,还要先保存这部分数据并安排合并,不能直接丢弃。
若正确 daemon 上已找不到旧卷,或数据只在已删除容器的可写层,就转查异地备份、数据库备份及云盘快照。没有备份时,减少源盘写入并评估专业恢复;重新创建同名卷不会找回旧内容。
本文的配置验证范围是 Compose 静态解析:比较两个项目名下的卷名称、external 固定卷引用,以及 bind mount 禁止自动建目录的设置。未进行真实卷重挂、数据库启动或数据恢复实测。
