Перенос Docker-образов между реестрами: пошаговая миграция без потери целостности | AdminWiki

Перенос Docker-образов между реестрами: пошаговая миграция без потери целостности

02 августа 2026 11 мин. чтения
Содержание статьи

Перенос образов между Docker-реестрами - это копирование манифестов, слоев и тегов из исходного хранилища в целевое с полным сохранением структуры. Ключевой инструмент для этой задачи - skopeo copy. Он работает напрямую между registry API, не требует загрузки образа на локальный диск и сохраняет все метаданные, включая мультиархитектурные манифесты. Если вам нужно перенести десятки или сотни образов между Docker Hub, Harbor или частным реестром, эта статья даст вам готовые команды и скрипты.

Мы разберем три основных сценария: миграция из публичного Docker Hub в корпоративный Harbor, копирование мультиархитектурных образов через crane и перенос в изолированную среду без доступа к интернету. Каждый сценарий проверен на практике в актуальных версиях ПО на август 2026 года.

Если вы планируете более масштабную миграцию инфраструктуры, включая Kubernetes и CI/CD, обратитесь к нашему руководству по миграции систем для DevOps. Там разобраны стратегии переноса ВМ, контейнеризации и перехода между облаками.

Зачем переносить образы между реестрами: типичные сценарии

Потребность в миграции образов возникает по конкретным техническим и организационным причинам. Вот основные ситуации, в которых вы оказываетесь перед задачей переноса.

Смена хостинга или облачного провайдера. Вы переезжаете с одного облака на другое и забираете с собой все накопленные образы приложений. Простой docker pull с последующим docker push для сотен образов занимает часы и требует свободного места на диске. Прямой перенос между реестрами решает эту проблему.

Переход на частный реестр Harbor. Компания внедряет корпоративный registry с управлением доступом, сканированием уязвимостей и политиками хранения. Все образы из публичного Docker Hub или старого приватного registry нужно перенести в новый Harbor-инстанс.

Создание автономной среды. Контуры без выхода в интернет - обычная практика в банках, госсекторе и промышленности. Образы доставляются на физическом носителе и загружаются в локальный реестр.

Репликация между средами dev/staging/prod. Образ, прошедший тестирование в staging, должен попасть в production-реестр без пересборки. Прямое копирование гарантирует, что в проде окажется тот же самый артефакт, который проверяли тесты.

Эта статья покрывает все перечисленные кейсы. Выберите свой сценарий и применяйте соответствующие команды.

Обзор инструментов для переноса Docker-образов

Для копирования образов между реестрами существует четыре основных инструмента. Каждый решает свою задачу. Выбор зависит от типа образов, доступности сети и требований к автоматизации.

Skopeo: прямой перенос без посредников

Skopeo - это утилита из экосистемы контейнерных инструментов, которая работает с registry API напрямую. Ее главное преимущество: образ не скачивается на локальный диск. Данные передаются от исходного реестра к целевому через вашу машину транзитом, в оперативной памяти. Это экономит место и время.

Установка на Debian/Ubuntu:

sudo apt update && sudo apt install -y skopeo

Базовый синтаксис для копирования:

skopeo copy docker://source-registry/image:tag docker://dest-registry/image:tag

Skopeo сохраняет все: манифест, слои, теги, аннотации. При копировании мультиархитектурного образа он переносит манифест-лист целиком, включая все платформы. Для работы с реестрами, требующими аутентификации, утилита использует файлы ~/.docker/config.json или переменные окружения REGISTRY_AUTH_FILE.

Пример переноса из Docker Hub в Harbor:

skopeo copy \
  docker://docker.io/library/nginx:1.25 \
  docker://harbor.company.com/project/nginx:1.25

Для промышленной миграции skopeo - основной инструмент. Он быстрее связки docker pull/push в 2-3 раза на больших объемах, поскольку не распаковывает слои на диск.

Crane: работа с мультиархитектурными образами

Crane - часть проекта Google go-containerregistry. Его специализация - операции с манифест-листами и мультиархитектурными образами. Если вы собираете образы для x86_64 и ARM64 под одним тегом, crane гарантирует, что после копирования все архитектуры останутся доступны.

Установка:

go install github.com/google/go-containerregistry/cmd/crane@latest

Копирование мультиархитектурного образа:

crane copy \
  docker.io/myuser/multiarch-app:latest \
  harbor.company.com/project/multiarch-app:latest

Проверка манифест-листа после переноса:

crane manifest harbor.company.com/project/multiarch-app:latest

Вывод покажет JSON с секцией manifests, где перечислены все платформы и их digest'ы. Skopeo тоже умеет копировать multi-arch образы, но crane предоставляет более детальный контроль над манифестами и удобнее для отладки.

Docker CLI: когда без него не обойтись

Стандартный Docker CLI остается актуальным в двух случаях: работа в air-gapped среде, где нет прямого доступа к реестрам, и окружения, где невозможно установить дополнительные утилиты.

Метод состоит из трех шагов:

  1. Выгрузка образа в tar-архив на машине с доступом к исходному реестру
  2. Перенос архива на целевую машину
  3. Загрузка в локальный реестр

Пример для air-gapped среды:

# На машине с доступом к интернету
docker pull nginx:1.25
docker save nginx:1.25 -o nginx-1.25.tar

# Перенос файла любым способом (scp, флешка)
scp nginx-1.25.tar user@airgap-host:/tmp/

# На изолированной машине
docker load -i /tmp/nginx-1.25.tar
docker tag nginx:1.25 localhost:5000/nginx:1.25
docker push localhost:5000/nginx:1.25

Ограничения метода: двойное использование дискового пространства (tar-файл и распакованные слои), отсутствие прямой поддержки мультиархитектурных образов без дополнительных манипуляций, потеря метаданных реестра (дата публикации, автор).

Подготовка к миграции: аутентификация и инвентаризация

Перед запуском массового переноса нужно решить две задачи: получить полный список образов для миграции и настроить доступ к целевому реестру. Пропуск этого этапа приводит к неполному переносу и ошибкам аутентификации в середине процесса.

Получение полного списка образов и тегов

Для Docker Hub используйте REST API. Получение списка тегов публичного образа:

curl -s "https://hub.docker.com/v2/repositories/library/nginx/tags/?page_size=100" | jq -r '.results[].name'

Для Harbor API v2.0:

curl -s -u "admin:Harbor12345" \
  "https://harbor.company.com/api/v2.0/projects/myproject/repositories/nginx/artifacts" \
  | jq -r '.[].tags[].name'

Скрипт для выгрузки полного списка образов из Harbor-проекта в файл:

#!/bin/bash
HARBOR_URL="https://harbor.company.com"
PROJECT="myproject"
USER="admin"
PASS="Harbor12345"

curl -s -u "${USER}:${PASS}" \
  "${HARBOR_URL}/api/v2.0/projects/${PROJECT}/repositories?page_size=100" \
  | jq -r '.[].name' | while read repo; do
  curl -s -u "${USER}:${PASS}" \
    "${HARBOR_URL}/api/v2.0/projects/${PROJECT}/repositories/${repo##*/}/artifacts" \
    | jq -r --arg repo "$repo" '.[].tags[]?.name | "\($repo):\(.)"'
done > images_list.txt

Файл images_list.txt на выходе содержит строки вида myproject/nginx:1.25 - готовые к использованию в цикле переноса.

Настройка учетных данных для целевого реестра

Для Harbor создайте robot account с минимальными правами. В Harbor UI: Administration → Robot Accounts → New Robot Account. Выдайте права на pull/push в целевой проект. Сохраните сгенерированный токен.

Аутентификация через переменные окружения - безопасный метод для скриптов и CI/CD:

export DOCKER_USER="robot\$myproject+push"
export DOCKER_PASS="eyJhbGciOiJSUzI1NiIs..."
echo "${DOCKER_PASS}" | docker login harbor.company.com \
  --username "${DOCKER_USER}" \
  --password-stdin

Для skopeo можно указать учетные данные напрямую:

skopeo copy \
  --src-creds="user:pass" \
  --dest-creds="robot\$myproject+push:token" \
  docker://source/image:tag \
  docker://harbor.company.com/project/image:tag

Проверьте доступность целевого реестра перед началом миграции:

skopeo inspect docker://harbor.company.com/project/nginx:latest

Пошаговая миграция образов: практические сценарии

Три сценария ниже покрывают 95% реальных задач. Выполняйте команды последовательно, проверяя результат на каждом шаге.

Сценарий 1: из Docker Hub в Harbor через skopeo

Задача: перенести образ library/nginx:1.25 из публичного Docker Hub в корпоративный Harbor. Это самый частый запрос при внедрении частного реестра.

Предварительные условия:

  • Установлен skopeo (версия 1.13+)
  • В Harbor создан проект production
  • Настроен robot account с правами на push в production

Команда переноса:

skopeo copy \
  docker://docker.io/library/nginx:1.25 \
  docker://harbor.company.com/production/nginx:1.25 \
  --dest-creds="robot\$production+push:TOKEN"

Ожидаемый вывод:

Getting image source signatures
Copying blob sha256:a1b2c3d4...
Copying blob sha256:e5f6g7h8...
Copying config sha256:i9j0k1l2...
Writing manifest to image destination
Storing signatures

После выполнения зайдите в Harbor UI, откройте проект production и убедитесь, что образ nginx:1.25 отображается с корректным размером и digest'ом. Запустите проверочный контейнер:

docker run --rm harbor.company.com/production/nginx:1.25 nginx -v

Сценарий 2: миграция мультиархитектурного образа через crane

Задача: перенести образ, собранный под x86_64 и ARM64, без потери манифест-листа. Обычный docker pull/push на машине с одной архитектурой скопирует только ее слои.

Исходный образ: myuser/multiarch-app:latest в Docker Hub. Проверяем, что он действительно мультиархитектурный:

crane manifest docker.io/myuser/multiarch-app:latest | jq '.manifests[] | {platform: .platform, digest: .digest}'

Вывод покажет несколько платформ:

{
  "platform": {"architecture": "amd64", "os": "linux"},
  "digest": "sha256:abc123..."
}
{
  "platform": {"architecture": "arm64", "os": "linux"},
  "digest": "sha256:def456..."
}

Копируем с сохранением манифест-листа:

crane copy \
  docker.io/myuser/multiarch-app:latest \
  harbor.company.com/production/multiarch-app:latest

Проверяем результат в целевом реестре:

crane manifest harbor.company.com/production/multiarch-app:latest | jq '.manifests | length'

Вывод должен совпадать с исходным количеством платформ. Дополнительно убедитесь, что digest'ы манифестов платформ идентичны исходным - это гарантирует побайтовую идентичность слоев.

Сценарий 3: перенос в изолированную среду через docker save/load

Задача: доставить образы в air-gapped среду, где нет прямого сетевого соединения с исходным реестром. Образы передаются на физическом носителе.

Шаг 1 - на машине с доступом к интернету:

docker pull nginx:1.25
docker pull redis:7.2
docker save nginx:1.25 redis:7.2 -o images-bundle.tar

Шаг 2 - перенос архива на изолированную машину. Используйте scp, USB-накопитель или промежуточный файловый сервер.

Шаг 3 - на изолированной машине загружаем образы в локальный registry:

# Запуск локального реестра, если его нет
docker run -d -p 5000:5000 --name registry registry:2

# Загрузка образов из архива
docker load -i images-bundle.tar

# Тегирование и отправка в локальный реестр
docker tag nginx:1.25 localhost:5000/nginx:1.25
docker push localhost:5000/nginx:1.25
docker tag redis:7.2 localhost:5000/redis:7.2
docker push localhost:5000/redis:7.2

Особенность работы с тегами: при docker save сохраняется текущий тег образа. Если образ имеет несколько тегов, сохранится только тот, что указан в команде. Для сохранения всех тегов используйте скрипт с перебором тегов или переходите на skopeo при первой возможности.

Проверка целостности после переноса

Факт успешного копирования не гарантирует работоспособность образа. Два обязательных этапа проверки: сравнение digest'ов и функциональный тест.

Сравнение контрольных сумм

Digest - это SHA256-хеш манифеста образа. Если digest'ы в исходном и целевом реестре совпадают, образы побайтово идентичны.

Получение digest'а из исходного реестра:

skopeo inspect docker://docker.io/library/nginx:1.25 | jq -r '.Digest'

Аналогично для целевого:

skopeo inspect docker://harbor.company.com/production/nginx:1.25 | jq -r '.Digest'

Скрипт для автоматической сверки списка образов:

#!/bin/bash
SRC_REG="docker.io"
DST_REG="harbor.company.com"

while IFS= read -r image; do
  src_digest=$(skopeo inspect docker://${SRC_REG}/${image} 2>/dev/null | jq -r '.Digest')
  dst_digest=$(skopeo inspect docker://${DST_REG}/${image} 2>/dev/null | jq -r '.Digest')
  
  if [ "$src_digest" = "$dst_digest" ] && [ -n "$src_digest" ]; then
    echo "OK: $image"
  else
    echo "MISMATCH: $image (src: $src_digest, dst: $dst_digest)"
  fi
done < images_list.txt

Для мультиархитектурных образов проверяйте digest'ы всех платформ через crane manifest.

Функциональное тестирование перенесенного образа

Запуск контейнера и проверка базовой функциональности:

# Запуск в фоне
docker run -d --name test-nginx -p 8080:80 harbor.company.com/production/nginx:1.25

# Проверка логов на ошибки
docker logs test-nginx

# Выполнение команды внутри контейнера
docker exec test-nginx nginx -t

# HTTP-проверка
curl -I http://localhost:8080

# Остановка и удаление
docker stop test-nginx && docker rm test-nginx

Для production-образов добавьте в тест запуск штатного entrypoint и проверку открытых портов. Если образ содержит healthcheck, дождитесь статуса healthy:

docker run -d --name test-app harbor.company.com/production/app:latest
sleep 10
docker inspect -f '{{.State.Health.Status}}' test-app

Типичные ошибки и их решение

При переносе образов между реестрами возникают предсказуемые ошибки. Разберем причины и способы исправления.

Ошибка аутентификации при копировании

Симптом: authentication required или denied: requested access to the resource is denied.

Причины:

  • Не выполнена команда docker login для целевого реестра
  • Robot account в Harbor не имеет прав на push в целевой проект
  • Имя пользователя robot account указано без префикса robot$
  • Токен просрочен

Решение: проверьте права robot account в Harbor UI. Для skopeo укажите учетные данные явно через --dest-creds. Проверьте срок действия токена - по умолчанию в Harbor он составляет 30 дней. Для долгоживущих миграций создайте токен с большим сроком или обновляйте его по расписанию.

Несовместимость манифеста или потеря слоев

Симптом: manifest unknown, blob unknown или образ в целевом реестре имеет нулевой размер.

Причины:

  • Исходный реестр использует манифест Schema V1, а целевой принимает только V2
  • При копировании мультиархитектурного образа через docker pull/push скопировалась только одна платформа
  • Сетевой сбой при передаче слоев большого размера

Решение: для skopeo укажите формат манифеста принудительно:

skopeo copy --format v2s2 docker://source/image:tag docker://dest/image:tag

Для мультиархитектурных образов используйте crane вместо docker CLI. При сетевых сбоях skopeo автоматически повторяет попытки для каждого слоя - дождитесь завершения, прерывание процесса приводит к неполной загрузке.

Автоматизация миграции для промышленных реестров

Разовый перенос десятка образов выполняется вручную. Для сотен образов и регулярной синхронизации между средами нужна автоматизация.

Скрипт для массового переноса образов

Этот скрипт читает список образов из файла и последовательно выполняет skopeo copy с логированием результата:

#!/bin/bash
SRC_REG="docker.io"
DST_REG="harbor.company.com"
DST_PROJECT="production"
LOG_FILE="migration_$(date +%Y%m%d_%H%M%S).log"

# Файл со списком образов в формате library/nginx:1.25
IMAGE_LIST="images_list.txt"

while IFS= read -r image; do
  echo "[$(date)] Migrating: $image" | tee -a "$LOG_FILE"
  
  if skopeo copy \
    "docker://${SRC_REG}/${image}" \
    "docker://${DST_REG}/${DST_PROJECT}/${image##*/}" \
    --dest-creds="${HARBOR_USER}:${HARBOR_PASS}" 2>&1 | tee -a "$LOG_FILE"; then
    echo "[$(date)] SUCCESS: $image" | tee -a "$LOG_FILE"
  else
    echo "[$(date)] FAILED: $image" | tee -a "$LOG_FILE"
  fi
done < "$IMAGE_LIST"

echo "Migration completed. Log: $LOG_FILE"

Запускайте скрипт в screen или tmux для длительных миграций. При сбое повторите только для образов со статусом FAILED в логе.

Интеграция с CI/CD пайплайном

Пример задачи в GitLab CI для автоматического переноса образа после сборки:

migrate-to-production:
  stage: deploy
  image: quay.io/skopeo/stable:latest
  script:
    - skopeo copy
      --src-creds="${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD}"
      --dest-creds="${HARBOR_USER}:${HARBOR_PASS}"
      "docker://${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}"
      "docker://harbor.company.com/production/${CI_PROJECT_NAME}:${CI_COMMIT_TAG}"
  only:
    - tags

Альтернатива для регулярной синхронизации - встроенные правила репликации Harbor. В Harbor UI: Administration → Replications → New Replication Rule. Настройте pull- или push-репликацию между проектами с фильтрацией по тегам и расписанием. Этот метод не требует внешних скриптов и работает на уровне самого реестра.

Для комплексной миграции DevOps-стека, включающей перенос CI/CD, мониторинга и логирования, используйте наше руководство по миграции DevOps-инструментов. Там разобран поэтапный переход с Jenkins на GitLab CI и с Zabbix на Prometheus.

Если ваша задача - не просто перенести образы, а развернуть их в production-окружении с мониторингом и логированием, обратитесь к гайду по Docker в production. Он покрывает управление секретами, health checks и blue-green деплой.

Поделиться:
Сохранить гайд? В закладки браузера