容器是可以随时删掉重建的——镜像拉个新的、docker compose up -d 一下就回来了。但容器里的数据不行:博客的文章、数据库的记录、面板的配置、上传的文件,这些东西一旦随着容器或者卷一起没了,没有任何「重新拉取」的办法。上一篇 《如何在Linux部署Docker&Compose》 讲了怎么把服务跑起来,这篇专门解决后顾之忧:数据到底存在哪、怎么备份、怎么恢复、出事了怎么把整站搬到另一台机器上。
先把一句话刻在脑子里:容器可删,数据不行。 所有备份方案的设计,都是围绕着把「数据」和「容器」分开管理展开的。
三种数据存法对比
Docker 里给容器存数据一共三种方式,先看全貌:
| 存法 | 数据实际位置 | 谁管理 | 备份难度 | 典型场景 |
|---|---|---|---|---|
| 命名卷(volume) | 宿主机 /var/lib/docker/volumes/ 下 | Docker 全权管理 | 中(要用容器辅助打包) | 数据库数据、应用持久数据 |
| 绑定挂载(bind mount) | 宿主机任意指定目录 | 你自己 | 低(就是普通文件,直接打包) | 配置文件、网站静态文件、代码目录 |
| tmpfs | 宿主机内存 | Docker | 无法备份(重启即没) | 临时数据、敏感信息中转 |
再补两个容易混淆的点:
- 容器可写层(不挂任何东西、直接往容器里写文件)不算一种「存法」:容器一删就全没,而且性能差。任何想留住的数据不许只写在容器可写层里。
- 命名卷和绑定挂载的区别不在性能(Linux 上两者性能基本一致),而在管理方式:命名卷是 Docker 托管的「仓库」,绑定挂载是你自己当家做主的「自家柜子」。
再顺带说清两个容易踩的概念:
- 匿名卷:有些镜像在 Dockerfile 里写了
VOLUME指令,或者你 run 时只写了-v /data没给卷名,Docker 会自动生成一个名字是随机长字符串的匿名卷来接住数据。它能防止数据落进可写层,但名字不可读、没人记得住,时间一长docker volume ls里攒一堆不知道是谁的卷,备份时也容易漏掉。重要数据一律用显式命名的卷,匿名卷只配接无所谓的数据。用docker volume ls -f dangling=true可以列出所有没有容器在用的卷,逐个确认后再清理。 - 先看清家底再谈备份:动手备份前,先用
docker system df -v看一眼每个卷的实际大小和引用情况——哪个卷在用、占了多少空间、有没有早就该删的僵尸卷,这个命令一屏全交代清楚。备份空间要按卷的实际体积预留,别等打包到一半才发现目标磁盘不够。
选型口诀:
- 数据库、应用内部数据 → 命名卷,交给 Docker 管,省心且跨平台行为一致;
- 配置文件、要经常打开看/改的东西 → 绑定挂载,宿主机上直接能编辑;
- 临时的、丢了无所谓的 → tmpfs 或者干脆写可写层。
命名卷操作全套
基本命令
# 创建一个命名卷docker volume create mydata
# 列出所有卷docker volume ls
# 看某个卷的详情(挂载点、创建时间、被哪些容器用)docker volume inspect mydata
# 删除一个卷(必须先停掉并删除使用它的容器)docker volume rm mydata
# 清理所有没有容器在用的卷(危险,见下方警告)docker volume pruneWARNING
docker volume prune和docker system prune一样是「一键后悔药没有」类命令:它会删掉所有当前没有容器引用的卷——包括你只是暂时把容器删了、打算一会儿重建后继续用的卷。执行前务必确认里面没有要留的数据。日常清理宁可手动docker volume rm一个个来。
卷在磁盘上的真实位置
命名卷的数据实际存放在宿主机:
/var/lib/docker/volumes/<卷名>/_data/比如卷 mydata 的内容就在 /var/lib/docker/volumes/mydata/_data/ 里。知道这个位置是为了排障时能看一眼里面有什么,但不建议直接去改这个目录:
- Docker 假定这个目录只有它自己在管,你绕过它直接增删文件,权限和属主很容易被写歪,容器里再访问就报错;
- 容器运行中直接改,应用层面的缓存和锁完全不知情,数据一致性没保证;
- 有些存储驱动下这个路径并不长这样(比如用了远程卷插件),按路径操作的代码不可移植。
正确姿势永远是:通过容器去读写卷——下面的备份和恢复都是这么做的。
Compose 里怎么声明
services: db: image: postgres:16 environment: - POSTGRES_PASSWORD=example volumes: - db_data:/var/lib/postgresql/data # 短语法:卷名:容器内路径
volumes: db_data: # 顶层 volumes 段:声明本项目用到的命名卷注意一个关键细节:Compose 创建的卷名会带项目名前缀。假设项目名(目录名)是 myapp,你在文件里写的 db_data,真实卷名是 myapp_db_data。用 docker volume ls 看到带前缀的卷别慌,那不是多出来的野卷子。排障一节会讲这个前缀带来的经典问题和解法。
只读挂载和绑定挂载的写法也顺带记住:
volumes: - db_data:/var/lib/postgresql/data # 可读写(默认) - ./config:/etc/myapp/config:ro # 绑定挂载 + 只读绑定挂载的注意事项
绑定挂载把宿主机目录直接「投影」进容器,方便是方便,坑也最集中在这一块。
坑一:宿主机路径不存在时
docker run -v /host/path:/data ... 时如果 /host/path 不存在,Docker 会自动以 root 身份创建一个空目录。你以为挂的是自己准备好的目录,其实挂了个空的,应用写进去的数据落在一个 root 属主的陌生目录里。Compose 的短语法同样如此。用长语法可以明确禁止这种行为:
volumes: - type: bind source: ./config target: /etc/myapp/config read_only: true bind: create_host_path: false # 路径不存在就直接报错,而不是偷偷新建坑二:属主与容器内 uid 对不上(最经典)
容器里的进程用什么 uid 运行,取决于镜像,常见情况:
- 官方
postgres镜像里 postgres 用户是 uid 999; nginx镜像 worker 进程是 uid 101;- 很多镜像默认直接用 root(uid 0)。
绑定挂载不会做任何 uid 转换:宿主机上属主是 uid 1000 的文件,进了容器就还是 uid 1000。于是经典报错就来了——容器内进程是 uid 999,宿主机目录属主是 1000 且权限 755,进程没有写权限,直接 Permission denied 起不来。
排查和处理:
# 查宿主机上自己的 uid/gidid
# 查目录现在的属主(显示数字 uid/gid)ls -n /srv/myapp/data
# 把目录属主改成容器内进程需要的 uid(例:999)sudo chown -R 999:999 /srv/myapp/data两个原则:
- 不要无脑
chmod 777。777 能「解决」权限问题,也能让任何进程随便写你的数据,只是把报错换成了安全隐患。 - 能选的话优先用命名卷:Docker 创建卷时会把卷目录的属主初始化为镜像里对应路径的属主,上面这个坑基本不会出现——这也是数据库类服务推荐命名卷的原因之一。
坑三:只读挂载 :ro
还有个写法上的细节:Compose 里绑定挂载用 ./config 这种相对路径时,它是相对于 Compose 文件所在目录解析的,不是你执行命令时所在的目录。项目目录搬家时绑定挂载会跟着 Compose 文件走,这通常正是你想要的;但如果在别的目录用 -f 指定 Compose 文件,就要意识到相对路径的锚点也随之变了,排障时先确认锚点再怀疑文件丢了。
配置文件类挂载建议一律加 :ro:
- ./nginx.conf:/etc/nginx/nginx.conf:ro容器被攻破时写不进配置,也改不了你的站点文件,属于零成本的安全加固。注意 :ro 只约束容器这一侧,宿主机上你自己照样能改。
命名卷备份:标准做法
命名卷的数据不在你的项目目录里,没法直接 tar 项目目录了事。标准做法是起一个临时容器,把卷挂进去,在容器里把卷内容打包到挂进来的备份目录。听起来绕,其实一条命令:
# 在项目目录下先建好备份存放点mkdir -p ~/backups
# 备份卷 myapp_db_data(换成你自己的真实卷名)docker run --rm \ -v myapp_db_data:/data:ro \ -v ~/backups:/backup \ alpine \ tar czf /backup/db_data-$(date +%F).tar.gz -C /data .逐参数讲解,这条命令每一部分都有讲究:
docker run --rm:临时容器,用完自动删除,不留垃圾;-v myapp_db_data:/data:ro:把要备份的卷挂到临时容器的/data,加:ro只读——备份是读操作,只读挂载保证备份过程绝不可能误伤原数据;-v ~/backups:/backup:把宿主机的备份目录挂到容器的/backup,打包产物写这里,容器删了产物还在宿主机上;alpine:用最小的工具镜像即可,它自带tar。手边有ubuntu镜像也一样;tar czf /backup/db_data-$(date +%F).tar.gz -C /data .:在容器里执行打包。-C /data .的意思是「切到 /data 目录,把当前目录的内容打包」——打包的是内容而不是data这个目录本身,恢复时就不会多出一层目录(排障一节会讲这个经典失误);文件名里用$(date +%F)自动带上日期,如db_data-2026-11-15.tar.gz,一眼能看出是哪天的备份。
另一种等价写法是 --volumes-from,直接借用正在运行的容器所挂的全部卷:
docker run --rm \ --volumes-from myapp-db-1 \ -v ~/backups:/backup \ alpine \ tar czf /backup/all-volumes-$(date +%F).tar.gz -C /data .--volumes-from <容器名> 会把那个容器挂的卷按相同路径挂到临时容器上。适合一个容器挂了好几个卷、想一次全打走的情况;缺点是路径要以原容器的挂载点为准,写命令前先 docker inspect 确认清楚。新手建议优先用第一种 -v 卷名:/data 的写法,路径自己说了算,不容易错。
WARNING备份文件生成后立刻验一下,别等恢复时才发现包是坏的:
Terminal window # 能列出内容且不报错,包基本完好tar tzf ~/backups/db_data-2026-11-15.tar.gz | head# 顺手看一眼大小,0 字节或者小得离谱肯定有问题ls -lh ~/backups/
命名卷恢复:标准做法
恢复是备份的逆操作:先建一个空卷,再用临时容器把备份包解进卷里。
# 1. 创建目标卷(如果卷已存在且里面是旧数据,先确认再删掉重建)docker volume create myapp_db_data
# 2. 把备份包解进卷docker run --rm \ -v myapp_db_data:/data \ -v ~/backups:/backup \ alpine \ tar xzf /backup/db_data-2026-11-15.tar.gz -C /data
# 3. 验证:列一下卷里的内容,确认文件都在docker run --rm -v myapp_db_data:/data alpine ls -la /data要点:
- 因为备份时打的是「
/data里面的内容」,恢复时直接解到/data就严丝合缝,不需要也不应该加--strip-components; - 解包在容器里以 root 执行,文件属主会按包里记录的 uid/gid 还原(tar 默认保留属主),这正是我们想要的——原数据是 uid 999 的,回来还是 999;
- 恢复前如果目标卷不是空的,旧文件会和解出来的文件混在一起。稳妥做法是恢复到新建的空卷,确认无误后再让服务切换过去。
验证不能只停在「文件在」。最后一定要把真正的服务挂上这个卷启动起来,看它能不能正常读数据:
docker compose up -ddocker compose logs -f db # 看数据库是否正常完成初始化/恢复服务能起来、应用能读到历史数据,恢复才算完成。
最后强调一句容易被跳过的话:没实际恢复过的备份,等于没有备份。 备份文件能生成、能列表,不代表真出事时能还原——属主丢了、路径多一层、包在传输中损坏,都是恢复现场才会暴露的问题。建议每套备份流程第一次跑通后,立刻在一个临时卷上做一次完整的恢复演练,把命令和坑都验证一遍;之后每次大改备份脚本,也重复这个动作。演练花十分钟,换的是出事那天不用现学。
数据库备份的一致性:别直接拷文件
到这里有个必须单独讲清的问题:对正在运行的数据库,直接打包它的数据卷,得到的备份可能是一个坏档。
原因不难理解:数据库运行时,数据一部分在磁盘文件里,一部分还在内存和预写日志(WAL)里,文件之间的一致性是靠数据库自己的机制在崩溃恢复时拼回去的。你在任意一个瞬间把文件整包拷走,拷到的是「半截子状态」——运气好,数据库启动时能靠日志回放自愈;运气不好,就是损坏、丢数据或者干脆起不来。文件级热拷贝对普通文件型应用(博客附件、上传目录)问题不大,对数据库是赌博。
正确做法有两条路:
路线一:逻辑导出(推荐,热备)
用数据库自带的导出工具,得到的是一致的逻辑备份,服务不用停:
# PostgreSQL:导出为 SQL 文本docker compose exec db pg_dump -U postgres myapp > ~/backups/mydb-$(date +%F).sql
# MySQL:--single-transaction 保证导出期间一致性且不锁表docker compose exec db mysqldump -u root -p密码 --single-transaction myapp > ~/backups/mydb-$(date +%F).sql恢复时反过来喂回去:
# PostgreSQLcat ~/backups/mydb-2026-11-15.sql | docker compose exec -T db psql -U postgres myapp
# MySQLcat ~/backups/mydb-2026-11-15.sql | docker compose exec -T db mysql -u root -p密码 myapp逻辑导出的额外好处:跨版本、跨机器恢复都兼容(卷文件直接拷则不行,老版本的数据文件新版本可能拒绝加载)。
路线二:停机冷备(简单粗暴但可靠)
能接受短暂停机的个人服务,直接停掉数据库再打包数据卷,文件处于静止状态,一致性天然成立:
docker compose stop dbdocker run --rm -v myapp_db_data:/data:ro -v ~/backups:/backup alpine \ tar czf /backup/db_data-cold-$(date +%F).tar.gz -C /data .docker compose start db在 Compose 项目里固化成一条命令
把逻辑导出写进一个小脚本放项目里(比如 backup.sh),手动和定时任务都能调:
#!/bin/bashset -emkdir -p ./backupsdocker compose exec -T db pg_dump -U postgres myapp > ./backups/mydb-$(date +%F).sqlecho "备份完成:./backups/mydb-$(date +%F).sql"注意 docker compose exec 在脚本里要加 -T(不分配伪终端),否则在 cron 这类非交互环境里会报错。
WARNING还有一个隐蔽坑:
pg_dump出来的备份只包含数据库内容,不包含数据库的用户/角色等全局对象。要全量兜底可以用pg_dumpall(整实例导出)。个人项目多数只用默认用户,知道有这回事即可,真迁移整实例时再换pg_dumpall。
整站迁移实战:把服务搬到另一台服务器
前面几节的技能拼起来,就是完整的整站迁移。场景很常见:VPS 到期换商家、机器配置不够要升级、或者单纯想把几个服务合并到一台。下面以一个典型的 Compose 项目(应用 + PostgreSQL,项目名 myapp,目录 /opt/myapp)为例,全程走一遍。
整体思路一句话:搬三样东西——项目目录(含配置)、数据卷、镜像清单,数据库单独逻辑导出最稳。
第一步(旧机):停服并打包项目目录
先停服务,保证打包期间文件不再变化:
cd /opt/myappdocker compose down注意是 down 不是 down -v——-v 会把命名卷一起删掉,迁移现场千万别顺手加上。
打包整个项目目录(compose 文件、.env、绑定挂载的配置和数据都在里面):
tar czf ~/myapp-dir-$(date +%F).tar.gz -C /opt myappWARNING
.env文件里通常有数据库密码、密钥等敏感信息,它会被打进这个包。传输和存放时按敏感文件对待:只走加密通道(scp/rsync over SSH 本身是加密的)、别丢到任何公共网盘或公开仓库。
第二步(旧机):打包数据卷 + 导出数据库
命名卷逐个按前面的标准做法打包:
mkdir -p ~/backupsdocker run --rm -v myapp_db_data:/data:ro -v ~/backups:/backup alpine \ tar czf /backup/myapp_db_data-$(date +%F).tar.gz -C /data .数据库额外再做一份逻辑导出,双保险(卷包用于整卷还原,SQL 用于卷还原出问题时兜底)。逻辑导出要在停机前做,所以实际操作顺序是:先 pg_dump → 再 docker compose down → 再打目录包和卷包:
docker compose exec -T db pg_dump -U postgres myapp > ~/backups/mydb-$(date +%F).sql记录镜像清单,到了新机好核对拉的版本对不对:
docker compose images > ~/backups/images-list.txtcat ~/backups/images-list.txt第三步:传输到新机
# rsync 断点续传,大文件更稳rsync -avz -e ssh ~/myapp-dir-*.tar.gz ~/backups/ root@新机IP:~/migrate/
# 或者简单粗暴 scpscp ~/myapp-dir-*.tar.gz ~/backups/*.tar.gz ~/backups/*.sql root@新机IP:~/migrate/第四步(新机):装 Docker 并放回目录
新机先按 《如何在Linux部署Docker&Compose》 装好 Docker,然后:
mkdir -p ~/migrate && cd ~/migrate # 假设安装包都传到了这里
# 放回项目目录tar xzf myapp-dir-*.tar.gz -C /optcd /opt/myappls -la # 确认 docker-compose.yml 和 .env 都在第五步(新机):逐卷恢复
在启动服务之前先建卷并灌数据。注意:卷名必须和 Compose 将要使用的完全一致(带项目名前缀 myapp_db_data):
docker volume create myapp_db_datadocker run --rm -v myapp_db_data:/data -v ~/migrate:/backup alpine \ tar xzf /backup/myapp_db_data-*.tar.gz -C /datadocker run --rm -v myapp_db_data:/data alpine ls -la /data # 验一眼然后告诉 Compose「这个卷是现成的,别自己新建」——在顶层 volumes 段给它标 external: true:
volumes: db_data: external: true name: myapp_db_data为什么需要这一步、忘了会怎样,排障一节细说。
第六步(新机):启动并按清单验证
cd /opt/myappdocker compose up -ddocker compose ps # 所有服务都是 Updocker compose logs -f # 看一遍启动日志,有没有连不上数据库之类的报错最后逐项过验证清单,三查缺一不可:
| 查什么 | 怎么查 | 通过标准 |
|---|---|---|
| 数据 | 登录应用后台/查数据库记录数 | 文章、用户、历史数据都在且是最新 |
| 证书 | 浏览器打开站点、点小锁看证书 | HTTPS 正常、证书未过期(反代在宿主机或另一台时记得同步改指向) |
| DNS | dig +short 你的域名 或直接访问 | 解析已切到新机 IP 且已生效 |
DNS 切换有个时间差:建议迁移前一天先把域名 TTL 调小(比如 300 秒),切换当天生效快、回滚也快。全部验证通过、观察一两天没问题之后,再去注销旧机——旧机就是你最后的回滚方案,别急着删。
回滚预案也要在动手前想好:如果新机验证不过,最快的回滚就是把 DNS 解析切回旧机 IP(旧机在整个过程中只是停了服务、数据原封未动,切回后 docker compose up -d 即可恢复)。所以迁移的正确心态是:旧机全程只读冻结、新机随便折腾,任何一步不对劲都能全身而退。等新机稳定运行几天、且在新机上已经产生了一份新备份之后,旧机才算完成历史使命。
定时自动备份:脚本 + 轮转 + cron
手动备份靠不住,迟早会忘。把前面的命令固化成脚本,cron 每天跑,并只保留最近 7 份:
#!/bin/bash# 备份指定命名卷,只保留最近 7 份set -e
BACKUP_DIR=/root/backupsDATE=$(date +%F)# 要备份的卷,空格分隔,按自己机器上的真实卷名填VOLUMES="myapp_db_data myapp_uploads"
mkdir -p "$BACKUP_DIR"
for vol in $VOLUMES; do docker run --rm \ -v ${vol}:/data:ro \ -v ${BACKUP_DIR}:/backup \ alpine \ tar czf /backup/${vol}-${DATE}.tar.gz -C /data . echo "已备份 $vol"done
# 轮转:删掉 7 天以前的备份包find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -deleteecho "备份全部完成"chmod +x /opt/scripts/backup-volumes.sh
# 先手动跑一次,确认能生成包、包能打开/opt/scripts/backup-volumes.shls -lh /root/backups/挂到 cron,每天凌晨 3 点执行、日志留痕:
0 3 * * * /opt/scripts/backup-volumes.sh >> /var/log/volume-backup.log 2>&1脚本里用 -mtime +7 -delete 做轮转有个前提:备份文件名各不相同(带日期)且目录里只放这类备份包,别把别的重要文件堆在这个目录里。
还有个运维习惯:每周顺手瞄一眼 /var/log/volume-backup.log 和备份目录里最新包的日期。定时备份最怕的不是报错,而是悄悄停摆——cron 被误删、磁盘满了、卷名改了,脚本天天失败但没人看日志,等真要恢复时才发现最新备份是三个月前的。确认「昨晚的包在、大小正常」,这个动作只要十秒。
WARNING备份只存本机等于没完全备份。 磁盘损坏、机器被注销、误操作
rm,备份和原数据会一起没。脚本跑完后再加一步异地传输,哪怕只是传到另一台 VPS 或者对象存储:Terminal window # 脚本末尾追加:同步到另一台机器rsync -avz -e ssh /root/backups/ root@备份机IP:/root/backups-主站/原则是至少做到「两台机器、两份拷贝」,重要数据再考虑第三份到对象存储。
排障
恢复后服务起不来,日志报 Permission denied
九成是属主问题:备份打包或解包时如果没在容器里用 tar 而是手动 cp,或者解包工具没保留属主,文件会变成 root 或错误 uid 所有,应用进程(比如 uid 999 的 postgres)读不了自己的数据目录。
# 对比原卷属主:看数据目录里的文件是谁的docker run --rm -v myapp_db_data:/data alpine ls -ln /data
# 典型修复:把属主改回应用需要的 uid(以 PostgreSQL 的 999 为例)docker run --rm -v myapp_db_data:/data alpine chown -R 999:999 /data根治办法是全程用本文的 tar 流程(tar 默认保留 uid/gid),别混用别的方法搬数据。
卷名对不上:Compose 新建了一个空卷,数据「消失」了
现象:迁移后服务能起来,但里面是全新空数据;docker volume ls 里躺着两个长得像的卷。
原因就是前面说的项目名前缀:你在旧机手工建的卷叫 db_data,而 Compose 按项目名拼出来的真实卷名是 myapp_db_data,两者不是一个卷。Compose 找不到自己要的卷,就默默新建了一个空的。
解法二选一:
# 解法一(推荐):顶层 volumes 段声明用现成的卷volumes: db_data: external: true name: myapp_db_data # 写真实卷名# 解法二:建卷时就直接用 Compose 期望的名字# 即一开始就 docker volume create myapp_db_data 并往里恢复,# Compose 发现同名卷已存在会直接使用排查时先 docker volume ls 对一眼名字,能省下大量瞎猜的时间。
备份包解开多了一层目录
现象:恢复完进卷一看,数据不在 /data 根下,而是躺在 /data/data/ 里,应用自然读不到。
原因:打包时写的是 tar czf ... /data(打了目录本身),包里所有路径都带个 data/ 前缀;解到 /data 就成了套娃。本文备份命令用的是 -C /data .(打目录里的内容),从源头避开这个问题。
如果手上的旧包已经是带目录层的,恢复时用 --strip-components 削掉一层补救:
docker run --rm -v myapp_db_data:/data -v ~/backups:/backup alpine \ tar xzf /backup/旧包.tar.gz -C /data --strip-components 1打包到一半失败:no space left on device
备份本质是「读一份、再写一份」,目标磁盘至少要留出比源数据更大的空闲空间(压缩后通常会小不少,但日志、媒体这类已经压缩过的数据几乎压不动,要按原体积预估)。先用 docker system df -v 确认卷的实际大小,再 df -h 看备份目录所在磁盘的余量。空间实在紧张时,可以让 tar 直接通过管道交给 gzip 以上的压缩,或者把备份目录挂到另一块盘上,但别把备份打到和源卷同一块快满的盘上自欺欺人。
卷删不掉:volume is in use
docker volume rm 报在用,先查是谁在用:
docker ps -a --filter volume=myapp_db_data停掉的容器也算「在用」,把对应容器 docker rm 后再删卷。确认里面数据真的不要了再动手。
小结
- 数据存法三选一:数据库和应用数据用命名卷、配置和常看常改的文件用绑定挂载、临时的用 tmpfs;永远别把重要数据只留在容器可写层;
- 命名卷备份恢复的标准姿势是临时容器 + tar:备份时卷只读挂载、打包目录内容(
-C /data .)、文件名带日期,恢复时解到新建的空卷并启动服务验证; - 数据库不要赌文件级热拷贝:能逻辑导出就
pg_dump/mysqldump,或者停机做冷备; - 整站迁移搬三样:项目目录(含
.env)、数据卷、镜像清单,上线后过数据、证书、DNS 三查,旧机留几天再注销; - 备份要自动化:脚本 + cron + 只留最近 7 份轮转,并且至少再往另一台机器同步一份。
把这套流程跑顺,你对这台机器上所有容器都可以做到一句话:随便删,随便搬,反正数据丢不了。