最近项目因为甲方爸爸在企业内网环境中,无法直接访问互联网,这给Docker Compose管理的多服务应用部署带来了难题.如何将外网构建好的镜像安全、完整地迁移到内网服务器?刚好最近研究了一些方案,本文结合实际操作经验,梳理出一套完整的镜像迁移方案,帮你避开各类“坑点”。

三步走原则

无论迁移规模大小,Docker Compose镜像迁移的核心逻辑都可概括为“打包-传输-加载”三个步骤,这是确保镜像完整性和可用性的基础:

  1. 打包:在能访问镜像的机器(如开发机、外网服务器)上,将Docker Compose配置中关联的镜像打包为可传输的压缩文件,关键是保留镜像的仓库名和标签信息。

  2. 传输:通过SSH(scp命令)、移动存储设备(U盘/移动硬盘)或内网文件共享服务(NFS/Samba),将打包文件传输到目标内网服务器。

  3. 加载:在内网服务器上,使用Docker命令将打包文件重新加载为镜像,确保镜像标签与Compose配置一致,避免启动服务时出现“镜像不存在”的错误。

从单服务到多服务的灵活适配

根据服务数量和版本管理需求,可选择不同的迁移方案。以下方法均基于实际场景设计,适配从简单到复杂的迁移需求。

单服务镜像迁移

如果仅需迁移Docker Compose中的单个服务镜像,直接使用docker save命令是最高效的方式,能完整保留镜像标签。

1、明确镜像信息

先通过以下命令查看Compose配置中的镜像详情,确认镜像的仓库名和标签:

docker compose images
CONTAINER           REPOSITORY                    TAG                 PLATFORM            IMAGE ID            SIZE
serviceA       harbor.xxxx.com/project/serviceA   1.1.7               linux/amd64         97c3ae472db6        761MB

从输出中提取完整镜像名:harbor.xxxx.com/project/serviceA:1.1.7

2、打包镜像

使用docker save结合gzip压缩,生成体积更小的打包文件:

docker save harbor.xxxx.com/project/serviceA:1.1.7 | gzip > serviceA.tar.gz

该命令会将指定镜像打包并压缩为pigx-upms.tar.gz,避免直接保存为.tar文件占用过多空间。

3、传输与加载

  1. 通过scp命令或者其他方式将文件传输到内网服务器即可
  2. 复制 docker-compose.yml 文件
  3. 修改配置文件(可选)
  4. 解压并加载
gunzip serviceA.tar.gz && docker load -i serviceA.tar

# 验证
docker images | grep serviceA
  1. docker compose up -d启动服务

多服务批量迁移

当Docker Compose包含多个服务或者镜像时,手动逐个打包效率低下。通过docker compose images结合文本处理命令,可实现全服务镜像批量打包。

1、提取所有镜像名(仅打包各服务的最大版本)

使用sortawk提取镜像列表中的仓库名和标签,生成标准镜像格式

docker compose images | awk 'FNR > 1 {print $2":"$3}' | sort -t: -k2,2Vr | awk -F: '!a[$1]++' > all_images.txt 
# 或者
docker images | awk 'FNR > 1 {print $1":"$2}' | grep '你需要过滤的镜像名'| sort -t: -k2,2Vr | awk -F: '!a[$1]++' > all_images.txt 

命令解析:

  • docker compose images:列出Compose中所有服务的镜像信息.

  • awk 'FNR > 1 {print $2":"$3}':跳过表头(FNR > 1),提取第2列(仓库名)和第3列(标签),用冒号连接.

  • sort -t: -k2,2V: -t:: 指定字段分隔符为冒号: -k2,2: 指定排序的键是第 2 个字段(即版本号 tag),并且只按第 2 个字段排序 -V: 启用版本排序。这是关键,它会智能地识别版本号的数字部分进行排序(例如,1.1.10 会排在 1.1.9 后面),而不是简单的字典序排序. -r: 降序(从大到小)排序

  • awk -F: '!a[$1]++':以冒号为分隔符,用关联数组a记录仓库名($1)的出现次数,仅保留第一次出现的记录(此时为最大版本)。

  • > all_images.txt 将结果保存到文件,便于后续检查

2、批量打包所有镜像

通过xargs将镜像列表传递给docker save,实现批量打包:

cat all_images.txt | sort -u | xargs docker save | gzip > all-services.tar.gz

其中sort -u用于去重——若多个服务依赖同一镜像,仅保留一份,减少打包体积。

3、批量加载与启动

加载命令和步骤与单服务一致

避坑指南

镜像迁移中最容易遇到标签丢失、文件为空等问题,结合实际踩坑经验,总结以下解决方案:

镜像标签为<none>:手动补全标签

若打包时使用镜像ID而非完整镜像名,加载后会出现<none>:<none>的“悬虚镜像”。可以先查看镜像ID,再手动打标签。

docker images | grep "<none>"

# 手动打标签(格式:docker tag 镜像ID 完整镜像名)
docker tag 镜像id harbor.xxxx.com/project/serviceA:1.1.7

提取镜像列表为空:检查awk条件

执行docker compose images | awk 'FNR > 2 {print $2":"$3}'后文件为空,大概率是awk的行号条件错误。若docker compose images输出仅含“表头+数据行”(无分隔线),需将FNR > 2改为FNR > 1,避免跳过数据行。

最佳实践

  1. 规范镜像标签:避免使用latest标签,改用“版本号+Git Commit ID”(如1.1.7-abc123),确保镜像版本唯一可追溯,减少迁移时的版本混淆。

  2. 可以使用.env管理版本:在Docker Compose中通过${version}引用.env文件中的版本变量,迁移时只需同步.env文件,无需修改Compose配置。

总结

Docker Compose镜像迁移的核心是“保留完整镜像信息+适配实际场景”:单服务迁移用简单命令高效完成,复杂场景通过版本排序和脚本实现自动化。掌握本文的方法和避坑技巧,无论是内网部署还是多环境迁移,都能轻松应对。

如果你的迁移场景更复杂(如需要搭建内网私有Registry),欢迎在评论区交流,后续将补充规模化迁移方案。