1. Docker备份迁移:先从一张“快照”开始说起
我手上维护着一个内部项目,跑着十几个容器,数据库、网关、定时任务全在里面。有一天要换机房,要求所有服务完整迁移,不停机、不丢数据。当时第一反应就是:能不能把容器和镜像直接打包带走?于是我把 Docker 备份迁移的整套流程梳理了一遍,从 export、import 到 commit、save、load,挨个踩了一遍坑,最后总结出了这篇文章。如果你也在为“环境迁移”“镜像分发”“容器快照”发愁,这篇文章基本能覆盖你需要的全部知识点。
Docker 镜像和容器备份迁移,本质上解决的是两件事:一个是“把当前环境打包还原”,另一个是“把镜像文件从一个机器搬到另一个机器”。前者对应 export 和 import,后者对应 save 和 load,而 commit 则是中间那个“把容器变成镜像”的桥梁。这五个命令看起来简单,真正用的时候容易搞混,尤其是 export 和 save、import 和 load 的区别,很多人到离职都没想明白。我先把核心关系图放在脑子里:commit 是“容器→镜像”,save/load 是“镜像→文件→镜像”,export/import 是“容器→文件→容器(严格说,导入后不是容器,而是镜像)”。看完这篇文章,你至少能分清这四件事,并且在真实迁移场景中知道该选哪条路。
这篇文章适合谁?如果你是刚接触 Docker 的开发者,正在为“容器换机器跑不起来”发愁;或者你是运维、测试、交付工程师,需要频繁做环境复制和版本归档;又或者你只是想把本地折腾好的环境打个包发给别人——都值得往下看。文章会把这五个命令的原理、实操步骤、参数细节和常见坑全部拆开讲清楚,并提供一套可以直接“抄作业”的备份迁移方案。我用的是实际项目中的场景,理论上你在任何装有 Docker 的 Linux 服务器上都能复现。
2. 先分清五个命令的职责,别再傻傻分不清
2.1 export 和 import:把容器打包成文件,再还原成一个镜像
export 的意思是把一个“容器”的文件系统打包成一个 tar 文件。注意关键词:容器。也就是说,它是针对当前运行的或已停止的容器整个文件系统的快照,不包括容器配置、环境变量、启动命令等信息,只保留文件系统层。
对应的 import 则是把这个 tar 文件导入,形成一个新的镜像。注意:导入之后得到的是一张“镜像”,不是“容器”。你需要再根据这个镜像去创建容器。很多人在这里绕晕了,以为 export 出去、import 回来还是容器、还能直接跑——不是的。import 得到的镜像默认没有 CMD、ENTRYPOINT、ENV 等元数据,你得自己指定启动命令和参数。
举一个最典型的场景:本地跑了一个容器,里面装了很多依赖、配置也改好了,现在要迁移到服务器上。这时候用 export 导出文件,拷到服务器,再用 import 导入为一个镜像,最后 docker run 启动。简单粗暴,适合“只要文件系统结果,不在乎历史构建过程”的情况。
2.2 commit:把当前容器状态凝固成新镜像
commit 可以理解为“给容器拍一张快照,并把这张快照保存为一个新的镜像”。它会记录下容器的当前文件系统状态,并且把容器启动时的一些配置(比如 CMD、ENV、工作目录等)合并进新镜像。
但这里有个隐藏重点:commit 保存的只是“当前容器的状态”,不是“构建过程”。你用 Dockerfile 构建镜像时,每一步都记录在层里,可复用、可缓存;但 commit 产生的镜像只有一层“大杂烩”,没有中间层历史。这会造成两个后果:一是镜像体积可能偏大(因为无法复用基础层的缓存),二是镜像的“血缘关系”不清晰——如果你不知道这个镜像当初是怎么配出来的,后续维护会很痛苦。
所以 commit 的正确使用场景是:你临时在容器里改了点东西,想快速生成一个定版镜像;或者您需要把调试过程中的状态保留下来。它不是 Docker 推荐的日常镜像构建方式,更不能替代 Dockerfile。但它在备份迁移流程里有一个特殊作用:配合 save/load 使用,实现“容器完整迁移”。
2.3 save 和 load:镜像层面的文件打包与还原
save 是把一个或多个镜像打包成一个 tar 文件的过程。它保留完整的镜像元数据、标签、历史层。load 则是把 save 产生的 tar 文件重新载入到 Docker 中,恢复为完整的镜像。
这两兄弟在镜像分发、离线部署、环境迁移中是绝对的“黄金搭档”。save 出来的文件可以被 load 到任何一台装有 Docker 的机器,不需要依赖网络拉取,也不需要考虑 registry(镜像仓库)配置。只要目标机器是 Docker 环境,load 进去就能直接看到这个镜像。
关键区别再强调一遍:save/load 操作的是“镜像”,export/import 操作的是“容器文件系统”。前者保元数据,后者只保文件系统。前者的镜像可以正常 docker run 并根据原有 CMD 启动,后者则需要你自己手动配置启动参数。搞懂这句话,百分之八十的坑你都不会踩了。
2.4 工具选型:什么时候用哪套方案
| 场景 | 推荐命令 | 原因 |
|---|---|---|
| 把当前容器文件系统打包,还原成新镜像再启动 | export + import | 只关心文件系统结果,不要历史层 |
| 把自定义镜像整体迁移到新机器 | save + load | 保留完整镜像元数据,无需重新构建 |
| 修改了容器内部文件,想固化成新镜像 | commit | 快速保存当前状态,便于继续分发 |
| 多镜像批量迁移 | save + load(支持多个镜像) | 一次打包多个,效率高 |
| 跨架构迁移 | 无直接命令,需结合平台指定 | 架构不同,文件系统未必兼容 |
一句话总结:如果你要的是一台机器上的“运行结果”,用 export;如果您要按照原本的定义去“重建运行环境”,用 save。如果只是临时改个配置、补充个文件,commit 是最顺手的。
3. 实操:五种备份迁移场景一步步演示
3.1 场景一:用 export 导出容器,import 导入为新镜像
假设我有一个运行中的容器,名字叫 temp-container,它是在一张 Ubuntu 镜像基础上,手动装了一批 Python 依赖后起来的。我要把它迁移到另一台机器上。
先在源机器上执行导出:
docker export temp-container -o temp-container.tar或者使用重定向:
docker export temp-container > temp-container.tar执行完会生成一个 tar 文件。这个文件大小取决于容器文件系统里有多少数据。接下来把这个 tar 文件拷贝到目标机器,执行导入:
cat temp-container.tar | docker import - temp-image:latest或者直接指定输入文件:
docker import temp-container.tar temp-image:latest导入完成后,用 docker images 查看,能看到一张名为 temp-image:latest 的镜像。再启动它:
docker run -it temp-image:latest /bin/bash注意这里必须手动指定 /bin/bash 或者你需要的启动命令。因为你 import 出来的这个镜像,没有 CMD 信息,docker run 如果不带命令,就不知道该执行什么。
我在实践中发现,为了减少启动时的痛苦,可以在 import 的时候顺便指定一些元数据:
docker import --change "CMD /bin/bash" temp-container.tar temp-image:latest--change 参数支持 Dockerfile 指令的简写,比如 ENV、CMD、WORKDIR、USER 等。用这个方式可以提前把启动命令固化进去,后续 docker run 就友好很多。
3.2 场景二:用 commit 把修改过的容器保存为新镜像
这个场景很常见:容器跑起来之后,你可能进容器里改了配置文件,装了调试工具,或者拷入了一些本地文件,而这些修改并不想写进 Dockerfile(改了还需要重新构建,太耗时),这时 commit 就派上用场了。
操作方式:
docker commit temp-container temp-image:v2执行后,docker images 中会出现 temp-image:v2。这个镜像的 IMAGE ID 和原来的基础镜像不同,并且它的“历史”里只会有你 commit 的这一层。
如果你还想给镜像加上一些描述信息,可以用 -m 和 -a 参数:
docker commit -m "add debug tools and update config" -a "ops-team" temp-container temp-image:v3注意,commit 时容器可以运行中,但为了保证文件一致性,最好先暂停容器,或者至少确保没有正在进行中的写操作。否则可能会导出半个写到一半的文件,虽然多数情况下不致命,但确实存在隐患。
有一个特别好的实操习惯:commit 之前先用 docker diff 查看容器相对原镜像到底改了什么:
docker diff temp-container这个命令会输出三类变化:A(新增文件)、C(修改文件)、D(删除文件)。通过它,你能确认改动是否符合预期,也能在 commit 后写变更说明时有的放矢。
3.3 场景三:用 save 保存镜像,再 load 到另一台机器
假设我本地构建了一个叫 app-server:2.1 的镜像,现在要部署到一台不能访问外网的服务器上。没有任何仓库可用,唯一的传输途径就是拷贝文件。这时候 save 和 load 是唯一选择。
先保存:
docker save -o app-server-2.1.tar app-server:2.1也可以一次保存多个镜像:
docker save -o multi-images.tar app-server:2.1 nginx:1.25 redis:7.0生成 tar 文件后,把它传到目标机器。目标机器上执行:
docker load -i app-server-2.1.tarload 完成后,docker images 里就多出了 app-server:2.1,和源机器上的镜像完全一样,标签、环境变量、启动命令、历史层全部保留。接下来直接 docker run 即可,无需额外配置。
这里要提醒一句:save 的输出文件最好在文件名里带上镜像名和版本号。因为如果文件没有元信息,孤立地看一个 tar,你不知道里面是什么。save 出来的 tar 可以用 tar -tvf 查看内容,能直接看到 layer 目录和 manifest.json 等文件。
3.4 场景四:save/load 与 export/import 的选择对比实战
有一种较常见的情况:源机器上跑了一堆服务,每个服务都是基于同一个基础镜像改出来的,层叠了很多配置。我想把这些服务的“运行后状态”整体打包给同事,让他在本地快速跑起来。
我分别试过用 export 和 save 两种方案:
- export 方案:对每个容器分别 export,再 import 成镜像。速度最快,但导入后的镜像没有启动命令,同事拿到后得自己拼 docker run 参数,体验不好。
- save 方案:先在源机器上 commit 每个容器为新镜像,再 save 导出。多了一步 commit,但同事 load 之后,镜像的 CMD、ENV 都在,直接 run 就能跑,省了很多沟通成本。
最终我选择了第二种。虽然多一步 commit,但换来的是“开箱即用”的体验。对于团队协作、交付场景,这种“省心”非常值得。下面是两个方案的对比:
| 对比项 | export + import | commit + save + load |
|---|---|---|
| 操作步骤 | 导容器→导镜像 | 固化容器→导镜像→载镜像 |
| 启动命令 | 需手动指定 | 保留原始配置 |
| 元数据保留 | 不保留 | 完整保留 |
| 适用场景 | 快速迁移文件系统 | 分发给别人开箱即用 |
3.5 场景五:批量迁移整套环境(组合拳)
手上有十几台机器的老项目,里面容器很多,我用了两套方案组合:对常规服务用 save/load,对需要固化临时改动的容器用 commit + save/load。
具体流程:
- 用 docker ps -a 梳理出所有容器列表,判断哪些需要保留运行状态。
- 对普通镜像,直接 save 打包。
- 对修改过的容器,先 commit 成新镜像,再 save 打包。
- 把 tar 文件统一放到一个目录,scp 或 rsync 到目标机器。
- 在目标机器上循环执行 docker load -i 逐个导入。
- 导入完成后,用 docker images 核对数量,然后 docker run 启动。
这套流程在小规模迁移中非常实用。有人问我为什么不用 docker compose 重新编排?因为部分容器的内部状态是运行期产生的,比如临时缓存文件、业务生成的数据文件,重新编排后还得初始化,非常麻烦。而 save/load 直接把状态带过去了,启动即还原,效率极高。
4. 常见报错与排查心得
4.1 export 时容器没有正常文件系统怎么办
如果你对一个已经删除的容器执行 export,Docker 会报错,提示找不到容器。这属于低级错误,但我也栽过:当时用脚本批量导出,循环里容器名多了一个换行符,导致 docker export 一直报 No such container。排查方式是先跑 docker ps -a 查容器ID,再对比脚本变量是否多出空格或回车。用 tr -d '\n' 或者 shell 内置的 trim 处理一下就好。
4.2 import 后镜像启动不了,报 command not found
这个坑我们在场景一中详细讲过,import 的镜像没有 CMD,直接 docker run 会报“容器启动失败”或者卡住。解决办法有两个:一是 docker run 时手动指定命令;二是 import 时用 --change 设置 CMD。
举一个实际例子:我导出一个 redis 容器的文件系统,import 成镜像后直接 docker run,结果容器刚启动就退出了。后来我加了:
docker run -d --name redis-new temp-image:latest redis-server --appendonly yes这才正常。更优雅的做法是 import 时带 --change "CMD ["redis-server"]"。
4.3 load 后镜像标签是 : 怎么办
有时候你会发现 save 前明明有 tag,load 后却显示 : 。这种情况大多数是因为 save 时指定了镜像 ID 而不是 tag。你执行:
docker save image-id.tar <镜像ID>这样保存的文件不带 tag 信息,load 时自然没有名字。解决方法是 save 时务必带上“仓库名:标签”,比如:
docker save -o app.tar app-server:2.1还有另一种情况:save 前镜像存在多个 tag,load 时只会加载其中一个 tag。如果你想保留所有 tag,请针对每个 tag 分别 save 或者使用 docker save 后面跟上所有需要导出的 tag。
4.4 save 出来的 tar 文件体积巨大,传输慢
这个没有办法,镜像里的每一层、每一个文件都在里面,体积大是事实。但有几个优化技巧:
- 先 docker rmi 清理不用的悬空镜像,再 save。
- 用 docker export 导运行结果而不是 save 完整镜像,如果不需要保留历史层。
- 对 tar 文件进行压缩传输:gzip、xz 或者 zstd 都行。比如:
docker save app-server:2.1 | gzip > app-server.tar.gz目标机器上解压再 load 或者直接管道 load:
gzip -dc app-server.tar.gz | docker load注意:save 的内部结构已经是 tar 格式,你再打包一层 gzip,效率高但会额外消耗 CPU。对于超大镜像,用 zstd 压缩率通常比 gzip 更好,实测在相同体积下压缩时间更短。
4.5 commit 之后镜像体积异常增大的原因
有时候 commit 一个容器,产生的镜像会比你预想的大很多。这是因为容器运行过程中产生了大量日志和缓存文件,比如 /var/log、/tmp、/root/.cache 等。commit 会把这些全部包含进镜像。
我遇到过一次:容器跑了一段时间,日志文件积累了 3GB,commit 出来的镜像比基础镜像大了好几倍。后来我在 commit 之前先进容器清理日志和缓存,再退出并 commit,体积瞬间降下来了。
清理手段可以参考:
docker exec temp-container bash -c "rm -rf /tmp/* /var/log/*.log; apt-get clean"当然这只是一部分手段,具体清理哪些文件取决于容器里的应用。核心思路是:commit 前先把容器“瘦身”,让镜像我只保留需要的东西。
4.6 跨机器传输后容器启动报权限错误
docker 内部文件权限一般不会出错,但有一种情况要注意:你 save/export 的文件拷到目标机器后,用 docker import 或 docker load 如果是在不同用户下操作的,镜像内部文件权限不受影响,因为 Docker 是按镜像元数据还原文件系统权限的。但如果你在宿主机上用 tar 命令手动解包并改动了文件,然后再用某种方式导入,权限就可能乱了。正确做法是不要手动解包 Docker 镜像 tar,而是始终用 docker load/import 接管。
5. 深入理解几个关键机制
5.1 镜像层、容器层与文件系统快照
要真正搞懂这五个命令,你需要理解 Docker 的“层”概念。镜像由多个只读层堆叠而成,容器启动后会在最上面加一个可写层。docker 容器内所有修改都发生在这个可写层。
export 导出的其实是“容器可写层 + 所有只读镜像层合并后的文件系统视图”,并不区分哪些是镜像层、哪些是容器层。它最终呈现给用户的是一个完整的目录结构。import 则是把这个完整的目录结构打包成一个新的镜像层,然后作为一层压在一个空的临时镜像上。所以 import 得到的镜像,通常只有一层。save 则不同,它会保留原有的分层结构、每一层的 ID、以及各层的元数据和 tar 归档。
简单理解:export 像“给电脑做系统镜像”,你拷的是整块硬盘的最终内容;save 像“给软件打包安装包”,它保留了软件的安装历史和结构。两者各有用途,前者适合“整体搬移”,后者适合“软件分发”。
5.2 为什么 import 前最好了解原镜像的启动参数
import 得到的镜像是“无根无据”的,它没有 CMD、没有 ENTRYPOINT、没有 WORKDIR,也没有 USER。所以当你 docker run 时,Docker 找不到默认执行的程序,容器会直接退出。
以我的一次实际操作为例:我导入一个包含 Java 应用的镜像,启动时忘记了指定 java 命令,容器刚创建就退出了。我第一反应是“镜像坏了”,后来才发现只是缺少启动命令。用 docker inspect 查看导入后镜像的 Config 部分,发现 Cmd、Entrypoint 都是空。于是我用 docker run 手动执行启动脚本解决了。
这里给出一个保险建议:如果你要 export 一个运行中的容器,先记下它的启动命令:
docker inspect temp-container --format='{{.Config.Cmd}}' docker inspect temp-container --format='{{.Config.Entrypoint}}' docker inspect temp-container --format='{{.Config.Env}}'迁移时如果选择 import 方案,就用这些信息补全启动命令;如果选择 commit + save,就不需要额外操心,元数据会随着镜像一起保存。
5.3 数据卷、临时文件与容器可写层的区别
绝对不要以为 export 出来的 tar 文件包含容器内的所有数据。有一个关键机制:挂载的数据卷(volume)以及 bind mount 的宿主机目录,默认不会包含在 export 的文件里。
举个例子:你的容器通过 -v /host/data:/app/data 挂载了宿主机目录,容器读写 /app/data 实际是在写宿主机的 /host/data。export 时,Docker 不会把 /host/data 打包进 tar,因为对容器来说,它只是一个挂载点,不属于容器文件系统。
这意味着,如果你做备份迁移,只 export 容器而不管数据卷,迁移后的新容器就会丢失所有业务数据。你必须额外备份数据卷内容。我之前就吃过这个亏,迁移一个数据库容器,以为 export 就万事大吉,结果新环境启动后库是空的。当时真是欲哭无泪。
正确做法是:先 docker inspect 看 Mounts 部分,列出所有挂载点,然后再单独备份宿主机目录或数据卷:
docker inspect temp-container --format='{{json .Mounts}}'然后对每个 source 路径进行 tar 打包传输。迁移完成后,在目标机器上按原样挂载回去。
5.4 压缩格式与 tar 内部结构
save 产出的文件本质上是一个 tar 归档,里面包含多层目录结构。你可以用 tar -tf 查看:
tar -tf app-server.tar会看到类似:
0d4f7a1e.../ 0d4f7a1e.../json 0d4f7a1e.../layer.tar 0d4f7a1e.../VERSION manifest.json repositoriesmanifest.json 里记录了镜像的配置文件和每个层的顺序。load 的时候,Docker 就是通过它还原镜像的。不要尝试去手动修改这些文件,除非你非常熟悉内部规范。否则一个 JSON 格式错误,整个镜像就无法加载。
export 出来的 tar 则完全不同,它没有 manifest.json、没有层信息,就是简单的文件系统目录的 tar 包。你可以直接把它解压出来看内容:
mkdir tmp && tar -xf temp-container.tar -C tmp这种可读性是 export 的一大优势——你可以确认打包结果是否符合预期,甚至可以修改里面的文件后再重新打包导入。对于某些奇技淫巧,比如临时改配置文件、替换某个二进制,挺有用。
6. 备份迁移的完整方案设计
6.1 确认边界:哪些要迁、哪些不迁
做迁移之前,先梳理一份清单:
- 需要迁移的镜像有哪些,包括 tag。
- 需要迁移的容器有哪些,运行参数是什么。
- 容器里是否挂载了数据卷,数据卷的位置和备份方法。
- 网络配置:容器使用的网络模式、端口映射、links/network 关联关系。
- 环境变量、启动命令、日志配置。
这个清单看起来繁琐,但做一次就能避免很多意外。我曾经接到一个迁移任务,上来就 docker save 了一批镜像,结果发现目标机器上容器起不来,因为容器部署时用了自定义网络,迁移环境里根本没有这个网络。后来我用 docker network create 先建立同名网络,再 docker run 挂上,问题才解决。
6.2 标准迁移流程
我总结了一套自己常用的标准迁移流程,可以直接套用:
- 停掉不需要持续运行的容器,避免导出时数据不一致。
- 使用 docker ps -a 和 docker inspect 获取全部容器信息。
- 对数据卷单独备份。
- 对需要保留运行状态的容器,先 commit 成镜像。
- 用 docker save 将相关镜像打包,包括依赖的基础镜像。
- 传输 tar 文件到目标机器。
- 在目标机器上 docker load 全部镜像。
- 检查 docker images,确认 tag 完整。
- 创建网络、创建数据卷、启动容器。
- 用 docker logs 和端口探测验证服务正常。
这套流程我用了很多次,从单机小项目到十几台机器的服务集群都验证过。当然,如果有 Docker Registry 或者镜像仓库,能推拉镜像当然更方便,但在离线环境、外网受限场景下,save/load 依然是不可替代的。
6.3 设计一个简单的自动备份脚本
如果你经常需要备份容器,可以写一个简单的脚本。以下是一个 bash 示例,备份指定容器为镜像并保存为带时间戳的文件:
#!/bin/bash container_name="$1" backup_dir="/backup/docker" timestamp=$(date +%Y%m%d%H%M%S) image_name="${container_name}-backup-${timestamp}" # 提交当前容器状态为新镜像 docker commit "${container_name}" "${image_name}" # 保存镜像为 tar 文件 docker save -o "${backup_dir}/${image_name}.tar" "${image_name}" echo "Backup complete: ${backup_dir}/${image_name}.tar"这个脚本核心步骤就两个:commit 和 save。如果你还需要备份数据卷,可以在脚本里加上对应的 tar 命令。定期执行的话,可以用 cron 或者 systemd timer 调度。
6.4 备份文件的安全存放与校验
备份文件属于敏感数据,可能包含业务代码、数据库内容、密钥等敏感信息。在文件传输环节,最好设置严格权限,例如:
chmod 600 backup.tar传输时优先使用 scp、rsync 或加密通道,避免通过不安全的渠道直接裸传。传输完成后,用 sha256sum 校验文件完整性:
sha256sum backup.tar > backup.tar.sha256目标机器上再校验一次,确认文件没有在传输中损坏。这一步很容易被忽略,但一旦 tar 文件损坏,load 时可能只报一个“格式错误”,你根本不知道是文件问题还是命令问题,排查成本很高。
7. 一个完整的迁移案例复盘
7.1 案例背景与现状梳理
某内部项目需要从旧服务器迁到新服务器,迁移的是三个容器:一个 Web 服务、一个任务调度器、一个缓存服务。旧服务器处于内网环境,没有外网访问权限,也无法使用公共镜像仓库。任务调度器和 Web 服务之间有自定义网络关联。
我先梳理了现状:
- Web 服务镜像:web:v1.2
- 任务调度器镜像:schedule:v3.0
- 缓存服务镜像:cache:redis-6
- Web 服务和调度器挂载了同一个数据卷,用于共享部分数据。
7.2 迁移执行过程
第一步,对运行中的容器做 commit,固化当前状态:
docker commit web-container web:v1.2-backup docker commit schedule-container schedule:v3.0-backup缓存服务不需要持久化,直接用原镜像即可。
第二步,备份数据卷:
docker inspect web-container --format='{{json .Mounts}}'结果显示挂载了一个数据卷 volume-web-data、一个宿主机目录 /opt/app/config。于是分别打包:
docker run --rm -v volume-web-data:/data:ro -v /backup:/backup alpine tar czf /backup/web-data.tar.gz -C /data . tar czf /backup/config.tar.gz -C /opt/app/config .第三步,save 镜像:
docker save -o /backup/images.tar web:v1.2-backup schedule:v3.0-backup cache:redis-6第四步,传输:
scp /backup/*.tar* user@target-server:/backup/第五步,在目标机器上 load:
docker load -i /backup/images.tar第六步,创建网络和数据卷:
docker network create app-network docker volume create volume-web-data第七步,恢复数据卷内容:
docker run --rm -v volume-web-data:/data -v /backup:/backup alpine tar xzf /backup/web-data.tar.gz -C /data第八步,启动容器。注意:容器要挂载到 app-network,并恢复原端口映射。
7.3 案例中的经验教训
这次迁移过程中有两点印象很深。第一,commit 之前必须确认容器内没在写重要数据。当时我直接 commit 了正在跑的任务调度器,结果它的临时文件状态被固化在镜像里,导致恢复后的调度器有一些残留状态。后来我在 commit 前先把容器优雅停掉,再 commit,体积更小,状态也更干净。
第二,save 多个镜像到一个 tar 时,如果某个镜像 tag 不存在,Docker 会直接报错,且不会生成部分文件。如果你逐个 save,可以查找是哪个镜像 tag 写错了。批量 save 虽然方便,但在排错时不够直观。
8. 备份迁移注意事项速查表
8.1 五条核心纪律
- 迁移前先确认是否有数据卷或挂载目录,单独备份。
- 确定目标机器的架构和源机器一致,否则镜像可能无法运行。
- 传输文件后先校验 checksum,再 load/import。
- 记录原始容器的启动参数、网络模式、环境变量,迁移时逐一恢复。
- 完成后用 docker logs 和 curl 端口探测验证服务。
8.2 具体命令速查
| 操作 | 命令示例 |
|---|---|
| 导出容器文件系统 | docker export 容器名 -o 文件名.tar |
| 导入容器文件系统为镜像 | docker import 文件名.tar 镜像名:标签 |
| 提交容器为镜像 | docker commit 容器名 镜像名:标签 |
| 保存镜像为文件 | docker save -o 文件名.tar 镜像名:标签 |
| 加载镜像文件 | docker load -i 文件名.tar |
| 查看容器挂载 | docker inspect 容器名 --format='{{json .Mounts}}' |
| 查看容器启动命令 | docker inspect 容器名 --format='{{.Config.Cmd}}' |
| 查看容器改动 | docker diff 容器名 |
8.3 安全与合规提醒
容器备份文件里可能包含数据库密码、API 密钥、业务代码等敏感信息。在存储和传输时,务必做好权限管理,避免把备份文件放到公开目录或未加密的共享盘。对包含敏感数据的备份文件,最好使用加密工具处理后再传输。如果备份文件不再需要,应及时删除,不要长期堆积在磁盘上。
9. 我对这套备份迁移体系的一些体会
做了这么多年容器迁移,我最强烈的感觉是:Docker 这五个命令看起来简单,但真正组合起来,是一套非常灵活、非常强大的“搬运系统”。export/import 适合快速复制文件系统;commit 适合把运行状态固化成镜像;save/load 适合完整镜像迁移。三组命令互相配合,几乎能覆盖日常所有迁移场景。
从设计哲学上看,commit 和 save 配合的方式,实际上是把“运行态的容器”转换成“可分发镜像”的关键路径。无论你是要把开发环境交付给同事,还是要把服务整体搬到新机房,都可以用这套组合完成。理解了镜像层、容器层、数据卷的区别之后,很多之前看起来很玄的报错会变得顺理成章——你不再需要去背命令,而是能根据“我要什么”“我有什么”这两个问题,自行推导出正确方案。
最后想提醒所有做容器运维的同行:无论你选择哪种备份方案,永远要留一份“冷备”文件,异地存放、定时更新。不要等到出事故才想起备份,这是我在实际运维中体会最深的一句话。
如果你在项目中使用过 Docker 的 export、import、commit、save、load,欢迎在评论区分享你的踩坑经历。如果你有迁移场景上的问题,也可以把具体情况写下来,我看到了会尽量回复。希望这篇总结对你的容器备份迁移工作有帮助。