TW●汤伟的数字生活NOTES ON A DIGITAL LIFE
个人数字生活文库始于 2026 · 持续修订总目录 ↗
全部文章

容器与自托管 / 实战手册

容器备份要能恢复:静态数据卷、数据库一致性与隔离演练

从数据清单和停写窗口开始,备份静态卷到归档,再恢复到新卷验证;解释数据库在线打包为何不可靠。

阅读设置字号仅保存在当前浏览器

先定义恢复目标和边界

本文按官方文档整理,核对日期为 2026-09-08,示例未实机验证。read 表示查询,包含仅查询的网络请求;write 表示新建文件、下载落盘或改变运行配置;danger 表示中断、替换、覆盖或删除。代码是供读者执行的操作说明。

本文延续项目 bloglab,工作目录与 compose.yaml 均与上一篇一致,静态服务 web 使用 bloglab_site 卷。演练需要足够磁盘空间、当前引擎操作权限、可写 backups 目录和事先下载好的 alpine:3.22 工具镜像;该标签仅为示例,应按自己的可信来源固定版本。命令全部是待执行说明,备份归档应包含演练数据,不能未经检查就用于真实数据库。先确定允许丢失多久的数据、最多能停机多久,再选择在线逻辑备份、应用原生备份或停机文件备份。

恢复一项服务通常需要四类东西:部署配置、准确镜像版本、持久化数据,以及能够重新获取的必要凭据。镜像导出不包含卷中数据,复制 compose.yaml 也不会复制上传文件。不要把完整环境变量渲染进公开备份清单;配置清单可以写明某项凭据从哪里安全取得,而不是存放其明文。本文不读取 .env 或设备密钥,真实备份若包含敏感业务内容,应由维护者决定加密、访问权限、离机副本和保留周期。

找出卷及所有容器引用bash · macOS Docker Desktop / Linux Docker Engine / DSM(先确认 docker compose 可用及参数支持)
查询 / 诊断
docker volume ls --filter name=bloglab_site
docker ps -a --filter volume=bloglab_site --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'

核对精确卷名;name 过滤可能匹配多个名字。必须确认是否有其他项目或一次性任务写入该卷。

准备工具与归档目录bash · macOS Docker Desktop / Linux Docker Engine / DSM(先确认 docker compose 可用及参数支持)
会修改状态
mkdir -p backups
docker pull alpine:3.22

mkdir 写文件系统,pull 联网下载并占用镜像存储;在维护窗口前准备,避免停机后才等待网络。

先列恢复清单,再决定备份是否合格

把本次静态卷练习看成一次小规模搬家:目标是即使原容器无法使用,也能依据配置、镜像和归档建立另一份可读页面。清单中应列出站点首页、至少一个资源文件、隐藏文件是否存在,以及需要保留的基本权限。对于只有默认欢迎页的空白练习,也应说明范围只有镜像初始化进去的文件,不能在报告里写成已经备份了个人博客或照片库。先定义范围,后面的验收才有明确对象。

归档之前除卷引用外,还要确认没有定时发布任务或另一个工具容器写入它。只读挂载到当前网页容器不等于全系统没有写入者,因为同一个卷可被别的容器读写挂载。本文的静态前提是维护者已经暂停所有更新渠道,并在归档和对照期间保持源文件不变;如果不能做到这一点,就应先取得应用支持的一致性副本。一次查询没有发现运行写入者,也不能阻止随后启动的任务,所以执行窗口仍要有明确控制。

归档文件名中的日期只是命名示意;真正执行时填写实际时间、时区、停写开始和结束时间。记录工具镜像引用及归档文件的完整路径,另写归档是否已复制到不同故障范围的位置。宿主同一磁盘下的备份目录适合演练,不足以承担整机损坏后的恢复。你可以先完成本地闭环,再把可信归档交给现有备份方案保存,而不是为了离机副本改变本篇的容器操作步骤。

确认卷的精确身份bash · macOS Docker Desktop / GNU/Linux Docker Engine;DSM 先核对插件能力
查询 / 诊断
docker volume inspect --format "{{.Name}} driver={{.Driver}}" bloglab_site

精确名称不存在就停止;不全量输出驱动选项或凭据。

文件存在不代表数据处于一致状态

运行中的数据库会同时修改数据页、事务日志和元数据。对正在写入的卷直接 tar,读到的不同文件可能来自不同时间点,打包退出码为零也不能证明事务一致。Docker 官方卷文档演示的是文件归档能力,不是对任何数据库的在线一致性保证。数据库应采用自身支持的逻辑导出或带恢复日志的物理备份流程,并遵守对应版本的恢复条件;本篇另查阅 PostgreSQL 文件级备份说明来划定一致性边界,不提供数据库备份实操命令。

如果采用冷备份,要停止全部写入者,并确认数据库正常关闭而非超时被强制结束;仅停止前端不能阻止后台任务继续写库。对涉及数据库和上传文件的一次业务操作,还要让两份数据对应同一业务时间点,否则可能出现数据库记录存在但附件缺失。快照也需要一致性策略,不能因为文件系统支持快照就省略应用协调。下面的归档只用于静态站点文件卷,数据库场景不得未经替换方案直接照抄。

停写之前记录计划,停写之后确认状态,归档完成后检查工具退出码与归档内容。若中途失败,先判断是否仍具备安全恢复业务的条件,不能在一个自动串联脚本里忽略错误后继续删除原始数据。这里把停止、归档、核验和恢复服务分成独立步骤,让每一步都有可观察的结果。

停止示例静态服务并核对bash · macOS Docker Desktop / Linux Docker Engine / DSM(先确认 docker compose 可用及参数支持)
高风险操作
docker compose -p bloglab -f compose.yaml stop -t 30 web
docker compose -p bloglab -f compose.yaml ps -a

会造成 web 中断;-t 30 是优雅退出等待秒数,超时可能强杀。仅确认全部写入者已停止后进入归档步骤。

只读挂载源卷,归档落到明确目录

执行前再次确认 bloglab_site 已存在且就是目标卷,因为 Docker 对不存在的命名卷可能自动创建,拼错名字会备出一个空卷。源卷以 readonly 挂载到 /source,宿主机当前项目的 backups 目录以绑定挂载方式进入 /backup。这个路径由引擎宿主机解释:若 context 指向远程主机,本地 pwd 不是远程主机的文件目录;本演练要求终端和存储路径位于同一宿主环境,Mac 使用 Docker Desktop 已允许共享的本机目录。

归档固定写为 site-20260908.tar.gz,重复演练应先选新文件名,并同步修改后续命令。命令拒绝覆盖同名归档,这只能防止普通重复操作,不替代并发锁;同一时间只运行一次。工具容器退出即删除,但归档文件留在 backups 中。由于源目录没有应用继续写入,示例静态文件才适合这种方式;如果你无法说明一致性如何获得,就先不要把生成的文件叫作可靠备份。

创建静态卷归档bash · macOS Docker Desktop / Linux Docker Engine / DSM(先确认 docker compose 可用及参数支持)
会修改状态
docker run --rm --pull=never --network none \
  --mount type=volume,src=bloglab_site,dst=/source,readonly \
  --mount "type=bind,src=$PWD/backups,dst=/backup" \
  alpine:3.22 sh -c 'test ! -e /backup/site-20260908.tar.gz && tar -czf /backup/site-20260908.tar.gz -C /source .'

仅适用于上述已停写静态卷。写归档并创建临时容器;--pull=never 防止维护中意外拉取,缺镜像就停止。

检查归档目录清单bash · macOS Docker Desktop / Linux Docker Engine / DSM(先确认 docker compose 可用及参数支持)
查询 / 诊断
tar -tzf backups/site-20260908.tar.gz

退出码应为零且应有预期站点文件。清单可读只证明压缩包能读取,仍需要恢复测试。

恢复原静态服务bash · macOS Docker Desktop / Linux Docker Engine / DSM(先确认 docker compose 可用及参数支持)
会修改状态
docker compose -p bloglab -f compose.yaml start web

只启动已经存在的原容器;确认归档结果后按维护计划恢复服务。会写运行状态并恢复对外响应。

先恢复到新卷,再连接隔离服务

恢复测试不能覆盖正在提供服务的卷。先查询 bloglab_restore_20260908 是否已存在,若存在就另选一个新名字;确认不存在后才创建。归档来自自己刚完成的可信备份,不能把来源不明的 tar 当成安全输入。解压时必须把目标挂载到新卷,把归档目录设为只读;把 source 与 target 写反会让一次演练变成数据事故,所以恢复类操作按 danger 标注,即使例子设计成隔离路径。

恢复后使用另一容器名和 18081 端口查看站点,保持原服务的 18080 不变。验证页面内容、关键文件数量、权限及应用可读取性,而不是只看容器能启动。示例静态页验证不够代表真实数据库恢复成功;数据库还需在匹配版本的隔离服务中完成原生恢复、校验记录及业务读写,并阻断通知、定时任务等外部副作用。恢复时间要记下来,超过业务容忍时间就需要调整备份设计。

确认并创建新的恢复卷bash · macOS Docker Desktop / Linux Docker Engine / DSM(先确认 docker compose 可用及参数支持)
会修改状态
docker volume ls --filter name=bloglab_restore_20260908
# 仅在确认该精确名称不存在后执行;已存在则改用新名
docker volume create bloglab_restore_20260908

第一条查询,第二条写 Docker 状态。create 对已有卷不会报空卷保证,因此不能省略前置检查。

恢复可信归档到隔离新卷bash · macOS Docker Desktop / Linux Docker Engine / DSM(先确认 docker compose 可用及参数支持)
高风险操作
docker run --rm --pull=never --network none \
  --mount type=volume,src=bloglab_restore_20260908,dst=/target \
  --mount "type=bind,src=$PWD/backups,dst=/backup,readonly" \
  alpine:3.22 sh -c 'test -z "$(ls -A /target)" && tar -xzf /backup/site-20260908.tar.gz -C /target'

目标必须是空的新卷。只使用可信归档;检查失败即停止,不要移除空目录检查来强行恢复。

查看恢复副本bash · macOS Docker Desktop / Linux Docker Engine / DSM(先确认 docker compose 可用及参数支持)
会修改状态
docker run -d --pull=never --name bloglab-restore-web \
  -p 127.0.0.1:18081:80 \
  --mount type=volume,src=bloglab_restore_20260908,dst=/usr/share/nginx/html,readonly \
  nginx:1.28.0-alpine

要求示例 Nginx 镜像已在本地、容器名及端口未占用。创建并启动测试服务,随后从引擎宿主机访问 http://127.0.0.1:18081。

恢复以后同时验收页面、文件和元数据

恢复服务在独立端口启动后,先分别请求原服务和恢复服务的首页。如果两个入口都能返回页面,还要检查恢复副本没有悄悄使用原卷:查询测试容器挂载的实际卷名,应当是新建恢复卷,且目标站点目录为只读。否则两个页面完全一样也可能只是读了同一份原始数据,根本没有验证归档。镜像采用相同版本,是为了避免把应用升级兼容性和数据恢复这两项实验混为一谈。

字节比较使用同时只读挂载两个卷的临时工具容器。下面示例只比较首页,用前面确认存在的资源文件替换路径后再抽样一次。比较返回成功说明这两个文件内容一致,不自动覆盖目录完整性;还需要对照归档清单,特别是隐藏项、资源目录和符号链接。若某个文件不存在,先核对它是否在备份范围内以及归档成员层级,不能跳过报错后宣称全量恢复成功。工具容器会创建运行状态,因此即使内部只读文件,也按 write 标注。

权限验收从实际用途出发:网页进程必须能读取恢复文件,目录必须能穿越,但不需要为了通过测试而给所有人写权限。普通工具镜像的归档能力不能代表完整复制了所有文件系统特性,尤其不能保证宿主的访问控制列表、扩展属性和厂商快照关系。Mac 上的文件摘要命令使用系统提供的方式,GNU/Linux 可使用对应 GNU 命令;DSM 是否具备相同选项需要先看本机帮助,不能把容器里的工具版本当成宿主版本。

请求恢复页面bash · macOS Docker Desktop / GNU/Linux Docker Engine;DSM 先核对插件能力
查询 / 诊断
curl --noproxy '*' --fail --show-error --connect-timeout 3 --max-time 10 http://127.0.0.1:18081/

read:查询静态页面;同时人工检查关键内容。

确认测试实例使用新卷bash · macOS Docker Desktop / GNU/Linux Docker Engine;DSM 先核对插件能力
查询 / 诊断
docker inspect --format '{{range .Mounts}}{{println .Name .Destination .RW}}{{end}}' bloglab-restore-web

卷名必须为本次恢复卷,不能是 bloglab_site。

比较源卷与恢复卷中的首页bash · macOS Docker Desktop / GNU/Linux Docker Engine;DSM 先核对插件能力
会修改状态
docker run --rm --pull=never --network none --mount type=volume,src=bloglab_site,dst=/source,readonly --mount type=volume,src=bloglab_restore_20260908,dst=/restored,readonly alpine:3.22 sh -c 'cmp /source/index.html /restored/index.html'

write:创建临时容器,两个数据挂载只读;cmp 非零时区分内容不同和读取失败。

失败也要有出口:保留源卷,重新建立合格副本

如果归档阶段空间不足,停止将该文件作为备份使用,保留报错并给下一次尝试分配新文件名;不直接覆盖失败产物,以便区分哪一份通过验收。只要原静态卷未改变且原服务配置仍有效,就可以按维护计划先恢复原服务,再处理空间问题。归档能列出成员但解包失败时,核对压缩包完整性和目标空间;原卷仍是恢复依据,不能因为文件名看起来完整就先删除它。

如果解包成功但网页报无权限,沿着恢复卷的文件模式、数字身份和目录穿越权限检查,先找差异再决定修复对象。如果页面缺少资源,比较归档层级与浏览器请求路径,判断是备份范围遗漏、恢复位置多套了一层目录,还是站点使用了绝对资源路径。每个分支只改变一个确定的问题,再重复原来的验收请求,这样失败记录才会逐步收敛,而不是出现一连串无法解释的尝试。

数据库边界另按 PostgreSQL 官方文件级备份说明核对:普通文件复制要求合适的关闭或一致性快照条件,恢复还需要完整的数据集合;不能只复制几张表对应的文件。这里引用它是说明应用一致性必须由具体产品保证,并不引入数据库操作教程。静态卷演练通过后,实际数据库仍需按其版本文档另外设计备份与恢复,不能用本例的比较命令代替事务和业务验收。

收尾时记录归档是否通过清单检查、恢复是否通过抽样、原服务是否恢复,以及测试容器与测试卷是否暂留。若要停止测试实例,只点名它,不对所有容器执行批量停止。保留期限取决于下一次演练和恢复材料的可用性,不能把“测试结束”理解成必须立即删除全部证据。未实机验证的卷驱动行为、元数据保留和异机兼容性也应留在记录中,供真正演练时逐项补齐。

停止独立恢复实例bash · macOS Docker Desktop / GNU/Linux Docker Engine;DSM 先核对插件能力
高风险操作
docker stop -t 30 bloglab-restore-web

danger:只中断测试入口;原 bloglab 服务不受该命令影响。

演练记录比备份成功提示更有价值

为每份归档保存时间、项目名、镜像引用、数据范围、停写方法和恢复结果。校验和可以检测拷贝损坏,但不能证明业务一致性;成功恢复到新卷并验证业务,才补上另一半证据。至少保留一份不依赖当前磁盘的副本,否则设备故障会同时带走原件和备份。保留周期应覆盖误操作发现延迟,更新后立即删掉上一份备份容易失去回退机会。

Mac 的命名卷由 Docker Desktop 管理,不要通过猜测虚拟机内部路径直接复制。Desktop 整体数据备份也有应用关闭等前置条件,不能把正在使用的磁盘镜像随手复制当作一致性保证。Linux 与群晖还需要考虑卷权限和实际文件系统支持的元数据;普通 tar 示例不能承诺保留所有 ACL、扩展属性或快照关系。实验卷和实验容器应在验证后单独核对清理,绝不把 compose down -v 或任何 prune --volumes 变成例行备份结尾。

示意恢复报告:数据范围为静态站点卷,停写方法为停止静态服务并暂停发布者;归档为 backups/site-20260908.tar.gz;恢复目标为 bloglab_restore_20260908;测试入口使用一八零八一端口;验收项目为挂载身份、首页字节和资源样本;原服务恢复到一八零八零端口;测试实例暂停保留。此报告描述一条完整演练路线,日期只是归档命名示例,真实退出码、摘要与耗时仍应来自实际执行。

已填写的示意材料text · macOS Docker Desktop / GNU/Linux Docker Engine;DSM 先核对插件能力
查询 / 诊断
归档失败:原卷保留 → 记录错误 → 原服务按计划 start → 新文件名重试
归档可读、恢复失败:保留归档和新卷 → 查空间/成员 → 不覆盖原卷
恢复成功、HTTP 失败:查测试挂载与目录权限 → 重复同一路径请求
验收通过:保留归档与离机副本 → 点名停止测试实例

阅读用示意材料;不是命令输出或实测记录,复制保存为记录文件属于 write。

延伸阅读

Volumes:生命周期、挂载与备份恢复 Back up and restore Docker Desktop data Bind mounts docker compose down docker image pull docker container inspect docker run:拉取策略与挂载参数 docker compose stop docker compose start PostgreSQL:文件系统级备份的一致性条件 curl 官方选项手册
同一专题 · 继续阅读容器、镜像、数据卷:从前端项目到可维护的 Compose 服务Docker 升级与回退:镜像替换之后,数据还能回去吗镜像拉不下来:按 DNS、证书、认证、限流、架构和代理逐层定位查看整个专题 ↗