Docker 开发镜像跨主机迁移:从 Ubuntu 20.04 到 24.04
摘要:说明如何把已验证的 Ubuntu 20.04 开发镜像迁移到 Ubuntu 24.04,并逐层验证 Docker、镜像、源码挂载和真实构建。
@[toc]
旧开发机长期使用 Ubuntu 20.04,交叉编译 SDK、CMake 和各种依赖已经调通;新机器升级到 Ubuntu 24.04 后,最稳妥的做法往往不是重装整套依赖,而是继续运行已经验证过的开发镜像。
1. 宿主机升级,不等于容器也升级
假设我们已经有一张镜像:
1 | cross-dev:20.04 |
它内部包含:
1 | Ubuntu 20.04 用户空间 |
把它迁移到 Ubuntu 24.04 后,结构是:
1 | flowchart TB |
容器不是完整虚拟机。它使用宿主机 Linux 内核,但镜像提供自己的用户空间文件系统。
所以在同一 CPU 架构下:
1 | Ubuntu 24.04 宿主机 |
完全是正常组合。
这正是旧项目需要的效果:宿主机更新了,但编译环境不跟着改变。
需要注意的是,容器并不能封装所有东西。USB、CAN、串口、GPU 等宿主机设备仍然依赖目标机器的驱动和 Docker 设备映射;如果源机器和目标机器 CPU 架构不同,也需要额外考虑多架构镜像或模拟。
2. 迁移时其实有三份数据
真正迁移之前,要先把下面三类内容分开:
1 | A. Docker Image |
其中 docker save 只负责 A。
如果以前运行容器时使用:
1 | docker run -v /home/devuser/work/demo-app:/workspace ... |
那么源码真正存储在宿主机:
1 | /home/devuser/work/demo-app |
容器里的 /workspace 只是 bind mount 入口。源码不会自动进入镜像归档。
因此迁移关系应该理解为:
1 | flowchart LR |
把这三个对象分开,后面很多问题会简单得多。
3. 先确认要迁移的镜像
旧机器上执行:
1 | docker image ls cross-dev:20.04 |
进一步确认:
1 | docker image inspect cross-dev:20.04 >/dev/null \ |
如果机器里同时存在:
1 | cross-dev:20.04 |
就不要只凭记忆操作。
4. 镜像迁移要用 save/load
导出:
1 | docker save -o cross-dev_20.04.tar cross-dev:20.04 |
导入:
1 | docker load -i cross-dev_20.04.tar |
这是镜像迁移的标准组合。
不要和下面这组混淆:
1 | docker export / docker import |
docker export 面向的是某个容器的文件系统快照,而 docker save 面向完整 Docker image,并保留镜像层和 tag 等信息。
对于开发环境迁移,应该记住:
1 | Image:save <-> load |
5. 大镜像导出前先看磁盘
交叉编译镜像如果包含完整 Yocto SDK,很容易达到二十多 GiB。
导出前检查当前目录:
1 | df -h . |
同时看 Docker 本身:
1 | docker system df |
这里存在两个不同的空间需求:
1 | Docker 保存镜像的数据 |
如果两者都在根分区,大镜像导出很容易把系统盘写满。
完成后查看实际文件:
1 | ls -lh cross-dev_20.04.tar |
不要直接用 docker image ls 的 SIZE 推断 tar 必然同样大;层共享和统计口径不同,最终以实际归档大小为准。
6. 大文件迁移最好带 SHA256
生成:
1 | sha256sum cross-dev_20.04.tar > cross-dev_20.04.tar.sha256 |
目标主机收到后:
1 | sha256sum -c cross-dev_20.04.tar.sha256 |
预期:
1 | cross-dev_20.04.tar: OK |
对于二三十 GiB 的文件,先做完整性校验可以避免 docker load 到后面才发现归档已经损坏。
7. 如何把归档传到新机器
小文件用 scp 很方便:
1 | scp cross-dev_20.04.tar \ |
大文件更推荐:
1 | rsync -avP cross-dev_20.04.tar \ |
rsync -P 可以显示进度,也更适合中断后的继续传输。使用移动硬盘时还要避开 FAT32 的单文件大小限制,大型归档更适合 exFAT、NTFS 或 ext4。
8. Ubuntu 24.04 上先验证 Docker Engine
新机器不要一上来就导入 20 多 GiB 镜像。
先执行:
1 | docker --version |
然后:
1 | docker run --rm hello-world |
当前 Docker 官方文档把 Ubuntu 24.04 LTS 列为 Docker Engine 支持系统。
这一阶段只确认新宿主机的 Docker 自己是否正常。如果 hello-world 都失败,就先处理安装、权限或 daemon 问题,不要把旧镜像引入排查范围。
9. 校验归档后再 docker load
确认 SHA256:
1 | sha256sum -c cross-dev_20.04.tar.sha256 |
然后:
1 | docker load -i cross-dev_20.04.tar |
正常会看到类似:
1 | Loaded image: cross-dev:20.04 |
检查:
1 | docker image ls cross-dev:20.04 |
如果归档已经使用 gzip、bzip2、xz 或 zstd 压缩,当前 Docker load 也支持直接读取压缩归档,不必先手动解成完整 tar。这个问题会在下一篇“大镜像导出导入工程化”中单独展开。
10. 导入以后先裸启动镜像
第一次测试不要挂项目:
1 | docker run --rm -it cross-dev:20.04 bash |
进入后:
1 | cat /etc/os-release |
应该仍然看到 Ubuntu 20.04。
然后检查核心工具:
1 | cmake --version |
如果镜像内置环境检查脚本,再执行:
1 | verify-dev-env |
这一步是在隔离变量:
1 | 不挂源码 |
如果这里通过,说明“镜像从旧主机迁到新主机”这个动作已经基本成功。
11. 源码要单独迁移
源码应该按照源码自己的方式迁移。
如果有完整远程仓库,最好重新 clone;如果有尚未推送的分支或本地修改,可以先检查:
1 | cd ~/work/demo-app |
再通过 Git 或 rsync 搬迁。
例如:
1 | rsync -avP ~/work/demo-app/ \ |
目标机器确认:
1 | cd ~/work/demo-app |
这里不要把“镜像成功导入”和“源码已经迁移”当成同一件事。
12. 新宿主机路径不必和旧主机完全相同
bind mount 的左边是宿主机路径,右边是容器路径。新主机的源码即使换了目录,只要仍挂到固定的 /workspace,容器内部看到的开发路径就可以保持不变:
1 | docker run --rm -it \ |
只有工程脚本硬编码了旧宿主机绝对路径时,才需要额外兼容。
13. 第一次编译不要复用旧 CMakeCache
跨主机迁移后,不建议直接把旧 build/ 继续拿来编译。
CMakeCache.txt 中可能缓存:
1 | 旧源码路径 |
更稳妥的是重新配置:
1 | rm -rf /workspace/build |
这样验证的是:
1 | 新宿主机 + 已迁移镜像 + 同一份源码 |
能否从零恢复构建,而不是旧缓存能否侥幸继续工作。
14. 最适合第一次迁移的验收顺序
建议固定成四层:
1 | 第一层:Docker Engine |
如果第二层失败,不查 CMake;如果第三层是权限问题,不急着改编译参数;只有前三层都正常,第四层才进入工程依赖排查。
这个顺序看起来保守,但它能让迁移故障非常容易定位。
15. 最终迁移模型
1 | flowchart LR |
整个过程真正被保存下来的是“已验证的开发环境”,而不是旧宿主机本身。
这也是容器化旧项目最实际的收益:宿主机可以更新,项目的用户空间、SDK 和编译工具不必同步升级。
参考资料
- Docker Docs:What is a container? — https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-a-container/
- Docker Docs:docker image save — https://docs.docker.com/reference/cli/docker/image/save/
- Docker Docs:docker image load — https://docs.docker.com/reference/cli/docker/image/load/
- Docker Docs:Install Docker Engine on Ubuntu — https://docs.docker.com/engine/install/ubuntu/











