迁移概述
适用场景:将一台服务器(B 机器)上的 Docker 镜像、容器配置、数据卷完整迁移至另一台服务器(A 机器)。
本次迁移包含以下核心资产:
| 资产类型 | 说明 |
|---|---|
| Docker 镜像 | mysql:latest、redis:latest、nginx:latest、portainer/portainer-ce:sts、docker-yidong-app:latest、osrf/ros:noetic-desktop |
| Docker Compose 配置 | 各服务的 docker-compose.yaml 及相关挂载目录 |
| 自定义网络 | prod-net(external 网络,用于容器间通信和固定 IP) |
| 数据卷/挂载目录 | 各容器的持久化数据目录 |
B 机器(源端)导出操作
导出镜像
正确做法:使用「镜像名:标签」导出,保留标签信息:
docker save -o all-images.tar \
mysql:latest \
redis:latest \
nginx:latest \
portainer/portainer-ce:sts \
docker-yidong-app:latest \
osrf/ros:noetic-desktop注意:严禁使用镜像 ID 导出,否则会导致标签信息丢失(详见踩坑记录 #1)。
打包配置文件与挂载目录
tar -czf docker-projects.tar.gz \
~/practical/portainer/ \
~/practical/nginx/ \
~/practical/mysql/ \
~/practical/redis/ \
~/practical/app/导出自定义网络信息
docker network inspect prod-net > prod-net-config.json传输至 A 机器
scp all-images.tar docker-projects.tar.gz prod-net-config.json user@A机器IP:~/practical/A 机器(目标端)导入与恢复操作
导入镜像
建议使用 DOCKER_BUILDKIT=0 避免兼容性问题:
DOCKER_BUILDKIT=0 docker load -i ~/practical/all-images.tar导入后验证镜像标签是否完整:
docker images解压配置文件
cd ~/practical
tar -xzf docker-projects.tar.gz重建自定义网络
创建 external 网络,指定子网(根据 prod-net-config.json 中的信息):
docker network create --driver bridge \
--subnet 172.18.0.0/16 \
prod-net依次启动服务
建议按依赖顺序启动:基础设施 → 应用 → 网关:
cd ~/practical/portainer && docker compose up -d
cd ~/practical/redis && docker compose up -d
cd ~/practical/mysql && docker compose up -d
cd ~/practical/app && docker compose up -d
cd ~/practical/nginx && docker compose up -d验证服务状态
检查所有容器是否正常运行:
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"踩坑记录与故障排查
问题 #1:镜像导入后标签全部丢失(显示 <none>:<none>)
现象:docker load 输出只有镜像 ID,docker images 中标签全部为 <none>:
Loaded image ID: sha256:dbbb94208ca8...
Loaded image ID: sha256:914b9d749556...REPOSITORY TAG IMAGE ID SIZE
<none> <none> dbbb94208ca8 263MB
<none> <none> 914b9d749556 934MB原因:在 B 机器执行 docker save 时,使用了镜像 ID 而不是镜像名:标签。使用 ID 导出时,tar 包中的 manifest.json 不包含 RepoTags 字段,导致 docker load 无法恢复标签。
解决方案:
步骤 1:从 tar 包中提取原始标签映射关系:
tar -xf all-images.tar -O manifest.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
for item in data:
tags = item.get('RepoTags', [])
config = item.get('Config', '')
if tags:
print(f'{config} -> {tags}')
"步骤 2:根据映射关系手动打标签,格式:docker tag <镜像ID> <仓库名>:<标签>:
docker tag dbbb94208ca8 docker-yidong-app:latest
docker tag 914b9d749556 mysql:latest
docker tag 329b288189cd portainer/portainer-ce:sts
docker tag 2481edb3016c redis:latest
docker tag 9f33606b3685 nginx:latest问题 #2:Portainer 启动失败 — 端口冲突
现象:
Error response from daemon: failed to set up container networking:
failed to bind host port 0.0.0.0:9000/tcp: address already in use原因:A 机器上 9000 端口已被其他服务占用。Portainer CE 新版的 Web UI 已通过 9443 (HTTPS) 端口提供,9000 端口(旧版 HTTP)已不再需要。
解决方案:编辑 docker-compose.yaml,移除多余的 9000 端口映射:
sed -i '/- 9000:9000/d' docker-compose.yaml
docker compose up -d修改后的端口配置应为:
ports:
- 9443:9443 # Web UI (HTTPS)
- 8008:8000 # Edge Agent
# 9000:9000 已移除问题 #3:Nginx 启动失败 — upstream 主机名解析失败
现象:
nginx: [emerg] host not found in upstream "portainer" in /etc/nginx/conf.d/default.conf:18容器反复崩溃重启。
原因:Nginx 在启动时立即解析 upstream 中的主机名。如果此时目标容器尚未启动完毕或不在同一 Docker 网络中,解析失败会导致 Nginx 直接退出。
解决方案(推荐:使用变量 + Docker DNS 实现延迟解析):修改 Nginx 配置,将 proxy_pass 改为变量形式。
原配置(启动时立即解析,失败即崩溃):
location /portainer/ {
proxy_pass https://portainer:9443;
}修改后(请求时才解析,支持动态重试):
location /portainer/ {
resolver 127.0.0.11 valid=10s ipv6=off;
set $upstream_portainer https://portainer:9443;
proxy_pass $upstream_portainer;
proxy_ssl_verify off;
}说明:127.0.0.11 是 Docker 内置 DNS 服务器,配合变量使用时支持动态解析。
最佳实践总结
推荐做法:
- 镜像导出:始终使用「镜像名:标签」而非镜像 ID
- 网络配置:使用
external: true网络,提前在目标机创建 - 服务依赖:在 compose 中使用
depends_on声明启动顺序 - Nginx 反代:使用 resolver + 变量实现延迟 DNS 解析
- 端口规划:迁移前检查目标机端口占用情况(
ss -tlnp)
避免做法:
- 用镜像 ID 导出:丢失所有标签信息,恢复极其麻烦
- 硬编码 upstream:Nginx 启动时目标不可用则直接崩溃
- 忽略 external 网络:容器无法获取预期 IP,容器间通信失败
- 同时启动所有服务:基础设施未就绪导致应用层启动失败
快速验证清单
迁移完成后,按以下清单逐项确认: