Docker 全量备份恢复实战:可离线、可迁移、可复原的标准方案

19 阅读10分钟

在日常运维里,Docker 的“导入导出”需求非常常见,通常集中在以下几类场景:

  • 需要对服务做一次 全量备份
  • 需要在新机器上做 完整恢复
  • 目标机器不能联网,要做 离线迁移
  • 需要把某个容器、镜像、数据卷打包后带到另一台服务器
  • 需要在升级、迁移、重装系统前保留业务状态

但很多人第一次接触时,很容易把下面几组命令混淆:

  • docker save / docker load
  • docker export / docker import
  • docker cp
  • docker volume
  • docker compose

这篇文章的目标不是罗列命令,而是用 Cookbook 的方式告诉你:

面对具体场景时,到底该用哪一组命令。


先说结论:Docker 的“完整迁移”到底要备份什么

如果你要迁移的是一个“可运行的服务”,而不是一个临时容器快照,那么完整备份通常应包括以下 3 类内容:

  • 镜像
  • 编排与配置
  • 数据

对应到 Docker 世界里,就是:

  • 镜像docker save / docker load
  • 编排与配置compose.yaml.env、配置文件目录
  • 数据:named volume 或 bind mount 的真实数据目录

所以最推荐的全量方案其实是:

  1. 导出镜像
  2. 备份 Compose 文件和配置
  3. 打包 Volume 或宿主机挂载目录
  4. 在目标机器上恢复镜像、配置和数据
  5. 再用 Compose 拉起服务

一句话总结:

不要把“导出一个容器”误认为“完整备份一个 Docker 服务”。


一张表看懂:到底该用哪个命令

目标推荐命令是否包含 Volume 数据是否适合服务迁移
导出镜像docker save
导入镜像docker load
导出容器文件系统docker export
导入为镜像docker import
复制单个文件docker cp部分辅助用途
备份 named volumedocker run -v volume:/data ... tar
备份 bind mounttar 宿主机目录

核心概念:save/load 和 export/import 到底有什么区别

这是最容易混淆的一组。

docker save / docker load

这组命令用于 镜像级别 的导出导入。

适用场景

  • 离线传输镜像
  • 内网环境部署
  • 多台机器之间复制镜像
  • 不能访问公网镜像仓库时分发镜像

特点

  • 保留镜像层信息
  • 保留 tag
  • 适合正式迁移和离线部署
  • 不包含容器运行状态
  • 不包含 volume 数据

示例:

docker save -o nginx.tar nginx:latest
docker load -i nginx.tar

docker export / docker import

这组命令用于 容器文件系统快照 的导出导入。

适用场景

  • 临时导出某个容器当前文件系统内容
  • 制作一个简单的新镜像基底
  • 调试、留档、分析

特点

  • 导出的是容器当前文件系统
  • 不保留镜像历史层
  • 不保留 Compose 配置
  • 不保留 volume 数据
  • 不适合完整迁移服务

示例:

docker export my-container -o my-container.tar
cat my-container.tar | docker import - my-image:latest

最重要的结论

如果你的目的是“离线迁移服务”或“完整备份恢复”,优先使用 save/load,而不是 export/import


Recipe 1:离线导出和导入镜像

这是最常见的一道“配方”。

场景

目标服务器不能联网,但需要运行某个镜像。

导出镜像

docker pull nginx:1.27
docker save -o nginx-1.27.tar nginx:1.27

如果要压缩:

gzip nginx-1.27.tar

得到:

nginx-1.27.tar.gz

传输到目标机器

可以使用:

  • scp
  • rsync
  • U 盘
  • 内部文件分发系统

例如:

scp nginx-1.27.tar.gz user@target:/opt/

目标机器导入

gunzip /opt/nginx-1.27.tar.gz
docker load -i /opt/nginx-1.27.tar

验证

docker images

Recipe 2:一次导出多个镜像

场景

一个服务栈包含多个镜像,例如:

  • nginx
  • redis
  • mysql
  • myapp

导出

docker save -o app-stack.tar \
  nginx:1.27 \
  redis:7 \
  mysql:8 \
  myapp:1.0

导入

docker load -i app-stack.tar

建议

如果你是在做一套离线交付包,推荐统一放到一个目录中:

backup_bundle/
├── images/
│   └── app-stack.tar
├── config/
│   ├── compose.yaml
│   └── .env
└── volumes/

这样最清晰。


Recipe 3:导出容器文件系统,但不用于服务迁移

场景

想保留当前容器内部改动,或者分析容器现状。

导出

docker export my-container -o my-container.tar

导入为镜像

cat my-container.tar | docker import - my-container-snapshot:latest

什么时候可以用

可以用于:

  • 保存实验环境
  • 导出某次调试后的临时系统状态
  • 生成一个轻量新镜像

什么时候不要用

不要用于:

  • 完整备份业务服务
  • 恢复数据库服务
  • 迁移含 volume 的应用
  • 替代 Compose 部署

因为它 不包含 volume 数据,也不保留运行编排信息


Recipe 4:完整备份一个 Compose 服务

这是最实用、也最值得收藏的一道“配方”。

场景

你有一个通过 Docker Compose 运行的服务,需要:

  • 全量备份
  • 异机恢复
  • 离线迁移
  • 重装系统前保留所有业务内容

备份对象

完整备份至少包括:

  • compose.yaml
  • .env
  • 应用配置文件
  • 镜像
  • volume 数据

第一步:准备备份目录

mkdir -p backup_bundle/{images,config,volumes}

第二步:备份 Compose 文件和配置

cp compose.yaml backup_bundle/config/
[ -f .env ] && cp .env backup_bundle/config/

如果还有配置目录,例如:

cp -r ./config backup_bundle/config/

第三步:导出镜像

先找出 Compose 使用的镜像:

docker compose config

然后导出镜像:

docker save -o backup_bundle/images/app-images.tar \
  nginx:1.27 \
  redis:7 \
  mysql:8 \
  myapp:1.0

第四步:备份 named volume

为什么 volume 要单独备份

因为:

  • docker save 不包含 volume
  • docker export 也不包含 volume

数据卷必须自己单独打包。

查看 volume 列表

docker volume ls

备份某个 volume

docker run --rm \
  -v myproj_mysql_data:/data \
  -v $(pwd)/backup_bundle/volumes:/backup \
  alpine \
  tar czf /backup/mysql_data.tar.gz -C /data .

再比如 Redis 数据:

docker run --rm \
  -v myproj_redis_data:/data \
  -v $(pwd)/backup_bundle/volumes:/backup \
  alpine \
  tar czf /backup/redis_data.tar.gz -C /data .

第五步:建议停服务后再做最终备份

如果应用正在写数据,备份出的数据可能不一致。

特别是以下类型服务:

  • MySQL
  • PostgreSQL
  • Redis
  • MongoDB
  • Elasticsearch

推荐在最终备份前执行:

docker compose stop

或者:

docker compose down

然后再重新执行 volume 打包。

备份完成后再启动:

docker compose up -d

Recipe 5:在目标机器上恢复完整服务

场景

你已经拿到了完整备份包,要在新机器恢复服务。

假设备份目录在:

/opt/backup_bundle

第一步:导入镜像

docker load -i /opt/backup_bundle/images/app-images.tar

第二步:恢复 Compose 文件

mkdir -p /opt/myapp
cp /opt/backup_bundle/config/compose.yaml /opt/myapp/
[ -f /opt/backup_bundle/config/.env ] && cp /opt/backup_bundle/config/.env /opt/myapp/

如果有配置目录:

cp -r /opt/backup_bundle/config/config /opt/myapp/

第三步:先创建 volume

进入项目目录:

cd /opt/myapp
docker compose up -d

这一步的主要目的是让 Docker 把 Compose 中声明的 volume 创建出来。

然后停掉服务,准备恢复数据:

docker compose stop

第四步:把备份数据恢复到 volume

恢复 MySQL 数据:

docker run --rm \
  -v myapp_mysql_data:/data \
  -v /opt/backup_bundle/volumes:/backup \
  alpine \
  sh -c "cd /data && tar xzf /backup/mysql_data.tar.gz"

恢复 Redis 数据:

docker run --rm \
  -v myapp_redis_data:/data \
  -v /opt/backup_bundle/volumes:/backup \
  alpine \
  sh -c "cd /data && tar xzf /backup/redis_data.tar.gz"

第五步:启动服务

cd /opt/myapp
docker compose up -d

验证

检查:

  • 容器是否正常启动
  • 端口是否监听
  • 业务是否可访问
  • 数据是否存在
  • 日志是否正常
docker compose ps
docker compose logs -f

Recipe 6:如果你使用的是 bind mount,如何备份和恢复

很多服务不是使用 named volume,而是直接挂载宿主机目录,比如:

services:
  app:
    volumes:
      - /data/app:/app/data
      - /data/logs:/app/logs

这种情况下,备份方式反而更简单。

备份

tar czf app-bind-data.tar.gz /data/app /data/logs

恢复

tar xzf app-bind-data.tar.gz -C /

注意

使用 bind mount 时,完整备份内容就变成:

  • 镜像
  • Compose 文件
  • 宿主机目录数据

而不是 Docker volume 本身。


Recipe 7:如何打包成一个可交付的离线安装包

如果你要把一套服务交付给客户、同事或另一台服务器,推荐把所有内容整理成一个标准目录。

推荐目录结构

offline_bundle/
├── images/
│   └── app-images.tar
├── config/
│   ├── compose.yaml
│   ├── .env
│   └── config/
├── volumes/
│   ├── mysql_data.tar.gz
│   └── redis_data.tar.gz
├── scripts/
│   ├── restore.sh
│   └── backup.sh
└── README.md

打包

tar czf offline_bundle.tar.gz offline_bundle

恢复时

只需:

  1. 解压
  2. 导入镜像
  3. 恢复配置
  4. 创建 volume
  5. 导入 volume 数据
  6. 启动服务

这是很适合企业内网、机房隔离区、生产环境交付的方式。


Recipe 8:用脚本实现一键备份

下面给一个通用思路的备份脚本模板。

#!/usr/bin/env bash
set -euo pipefail

PROJECT_DIR=/opt/myapp
BACKUP_DIR=/opt/backup_bundle

mkdir -p "$BACKUP_DIR/images" "$BACKUP_DIR/config" "$BACKUP_DIR/volumes"

cd "$PROJECT_DIR"

echo "==> 备份配置"
cp compose.yaml "$BACKUP_DIR/config/" || true
[ -f .env ] && cp .env "$BACKUP_DIR/config/" || true
[ -d config ] && cp -r config "$BACKUP_DIR/config/" || true

echo "==> 停止服务"
docker compose stop

echo "==> 导出镜像"
docker save -o "$BACKUP_DIR/images/app-images.tar" \
  nginx:1.27 redis:7 mysql:8 myapp:1.0

echo "==> 导出 volumes"
docker run --rm \
  -v myapp_mysql_data:/data \
  -v "$BACKUP_DIR/volumes":/backup \
  alpine \
  tar czf /backup/mysql_data.tar.gz -C /data .

docker run --rm \
  -v myapp_redis_data:/data \
  -v "$BACKUP_DIR/volumes":/backup \
  alpine \
  tar czf /backup/redis_data.tar.gz -C /data .

echo "==> 启动服务"
docker compose up -d

echo "备份完成:$BACKUP_DIR"

Recipe 9:用脚本实现一键恢复

#!/usr/bin/env bash
set -euo pipefail

PROJECT_DIR=/opt/myapp
BACKUP_DIR=/opt/backup_bundle

mkdir -p "$PROJECT_DIR"

cp "$BACKUP_DIR/config/compose.yaml" "$PROJECT_DIR/" || true
[ -f "$BACKUP_DIR/config/.env" ] && cp "$BACKUP_DIR/config/.env" "$PROJECT_DIR/" || true
[ -d "$BACKUP_DIR/config/config" ] && cp -r "$BACKUP_DIR/config/config" "$PROJECT_DIR/" || true

echo "==> 导入镜像"
docker load -i "$BACKUP_DIR/images/app-images.tar"

cd "$PROJECT_DIR"

echo "==> 创建 volumes"
docker compose up -d
docker compose stop

echo "==> 恢复数据"
docker run --rm \
  -v myapp_mysql_data:/data \
  -v "$BACKUP_DIR/volumes":/backup \
  alpine \
  sh -c "cd /data && tar xzf /backup/mysql_data.tar.gz"

docker run --rm \
  -v myapp_redis_data:/data \
  -v "$BACKUP_DIR/volumes":/backup \
  alpine \
  sh -c "cd /data && tar xzf /backup/redis_data.tar.gz"

echo "==> 启动服务"
docker compose up -d

echo "恢复完成"

Recipe 10:只复制容器里的单个文件

有时候不需要全量备份,只想拿出一个文件,比如日志、配置、证书。

这时可以用 docker cp

从容器拷贝到宿主机

docker cp my-container:/etc/nginx/nginx.conf ./nginx.conf

从宿主机拷贝到容器

docker cp ./nginx.conf my-container:/etc/nginx/nginx.conf

适用场景

  • 临时导出配置
  • 复制日志
  • 调整证书
  • 小范围修复

不适用

  • 不适合作为完整迁移方案
  • 不适合批量数据备份

常见误区

误区 1:docker export 可以完整迁移服务

不能。

它只导出容器文件系统,不包含:

  • volume 数据
  • Compose 配置
  • 网络声明
  • 环境变量
  • 启动命令的完整上下文

误区 2:docker save 会连数据一起导出

不会。

docker save 只处理镜像,不处理运行时数据卷。


误区 3:docker compose down 会自动删除数据

默认不会。

只有加上 -v 才会删除相关 volume:

docker compose down -v

这条命令非常危险,执行前一定确认。


误区 4:直接备份 /var/lib/docker 就行

虽然理论上可以做整机级灾备,但通常不推荐作为常规迁移方案,因为它:

  • 强依赖 Docker 版本
  • 强依赖存储驱动
  • 可移植性差
  • 跨机器恢复失败风险高

常规迁移还是应该采用:

  • 镜像导出
  • 配置保存
  • volume 数据单独打包

这种标准做法。


最佳实践清单

1. 服务迁移优先用 save/load

而不是 export/import

2. Volume 必须单独备份

镜像不等于数据。

3. Compose 文件必须纳入备份

没有编排文件,恢复后的服务往往不可复现。

4. 数据库类服务备份前尽量停写

避免产生不一致数据。

5. 离线交付建议做成统一目录包

镜像、配置、数据、脚本放在一起最利于维护。

6. 恢复流程要提前演练

真正有故障时,第一次恢复往往最容易出错。

7. 给备份包加版本号和日期

例如:

backup_bundle_2025-02-16_v1.tar.gz

这样便于追溯。


一套推荐的标准方案

如果你问我:Docker 如何做“全量备份恢复 + 离线迁移”的最佳方案?

我的标准答案是:

标准备份包应包含

  • compose.yaml
  • .env
  • 配置文件目录
  • docker save 导出的镜像包
  • 所有 volume 的压缩归档
  • 备份和恢复脚本
  • 一份 README

标准备份动作

  1. 停止服务或冻结写入
  2. 导出镜像
  3. 打包 volume
  4. 备份 Compose 和配置
  5. 统一归档

标准恢复动作

  1. 导入镜像
  2. 恢复 Compose 和配置
  3. 创建 volume
  4. 恢复 volume 数据
  5. 启动服务
  6. 做健康检查

这套方案的优点是:

  • 可离线
  • 可迁移
  • 可复现
  • 可审计
  • 可脚本化
  • 不依赖原机器环境过深

总结

Docker 的导入导出不能只盯着一个命令看,而应该根据对象来区分:

  • 镜像docker save / docker load
  • 容器文件系统快照docker export / docker import
  • 单个文件docker cp
  • 数据卷tar + 临时容器
  • 服务定义compose.yaml.env、配置目录

如果你的目标是:

  • 全量备份
  • 完整恢复
  • 离线迁移
  • 跨主机部署

那么最稳妥的方式永远是:

镜像 + 配置 + 数据 三者一起备份与恢复。

这才是 Docker 世界里真正意义上的“完整迁移”。