Базовые операции: push, pull и tag
Реестр образов - центральное звено в цепочке CI/CD. Без него невозможна воспроизводимая сборка и доставка приложений. Каждый раз, когда пайплайн завершает работу, артефакт попадает в registry, откуда его забирают продакшен-серверы. Освоение трёх базовых команд - push, pull и tag - закрывает 80% ежедневных задач DevOps-инженера. Начнём с аутентификации. Перед любой операцией с приватным реестром требуется логин. Для Docker Hub команда выглядит так:
docker login
Для самопального registry, развёрнутого на myregistry.local:5000, указывайте адрес явно:
docker login myregistry.local:5000
После ввода учётных данных Docker сохраняет токен в ~/.docker/config.json. Все последующие команды используют его автоматически. Если вы работаете с облачными решениями вроде Timeweb Cloud, аутентификация выполняется один раз при настройке окружения, а дальнейшая работа с образами идёт без повторного ввода пароля.
Тегирование образа перед загрузкой
Тег - это указатель на конкретную версию образа. Без правильного тега образ не попадёт в нужный репозиторий. Синтаксис команды:
docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
Предположим, вы собрали образ своего бэкенда и хотите отправить его в репозиторий myapp/backend. Сначала присвойте образу полное имя, включающее адрес реестра:
docker tag backend:latest myregistry.local:5000/myapp/backend:1.2.3
Теперь образ имеет два идентификатора: короткий backend:latest и полный путь в реестре. Обратите внимание: тег latest не означает «последняя версия» в хронологическом смысле. Это всего лишь значение по умолчанию, которое Docker присваивает, если тег не указан явно. В продакшене полагаться на latest опасно - вы никогда не знаете точно, какой билд развернётся на целевом сервере. Подробнее о стратегиях тегирования мы поговорим в отдельном разделе, а пока запомните правило: всегда указывайте конкретную версию или хеш коммита.
Отправка образа в реестр (push)
После тегирования образ готов к загрузке. Команда push отправляет все слои образа в указанный registry:
docker push myregistry.local:5000/myapp/backend:1.2.3
Docker начинает выгрузку слоёв. Если какой-то слой уже существует в реестре (например, базовый образ ubuntu:22.04), он не передаётся повторно - работает дедупликация на уровне content-addressable storage. Это экономит время и трафик.
Самая частая ошибка при push - отказ в доступе. Сообщение выглядит так:
denied: requested access to the resource is denied
Причины три: вы не выполнили docker login, ваш пользователь не имеет прав на запись в этот репозиторий, или реестр вообще не поддерживает аутентификацию в текущей конфигурации. Проверьте логин командой docker logout и повторите вход заново. Для самопального registry убедитесь, что в конфигурации включена базовая аутентификация через htpasswd. Если вы переносите образы между разными реестрами, например мигрируете с Docker Hub на собственное хранилище, готовые сценарии и команды для skopeo и crane собраны в отдельном руководстве по миграции.
Получение образа из реестра (pull)
На целевой машине, где будет запущен контейнер, образ скачивается командой pull:
docker pull myregistry.local:5000/myapp/backend:1.2.3
Если тег не указан, Docker запрашивает latest. Это поведение по умолчанию - ещё одна причина избегать плавающих тегов в production-окружении. При pull действует тот же механизм дедупликации: уже имеющиеся на хосте слои не загружаются повторно. Docker сравнивает локальные digest'ы с теми, что хранятся в реестре, и докачивает только недостающее.
Для публичных образов из Docker Hub указывать полный путь не нужно - достаточно имени и тега:
docker pull nginx:1.25-alpine
Эта команда скачает официальный образ Nginx на базе Alpine. Если вы работаете в изолированной среде без доступа к интернету, pull выполняется из внутреннего зеркала реестра. Настройка такого зеркала и синхронизация образов между реестрами - тема, которую мы подробно разбирали в статье про интеграцию реестра с CI/CD пайплайнами.
Поиск и получение списка образов в реестре
Рано или поздно реестр разрастается. Появляются десятки репозиториев, сотни тегов. Чтобы навести порядок, нужно сначала понять, что вообще хранится в registry. Docker CLI не предоставляет встроенной команды для листинга содержимого - эта функциональность доступна только через REST API.
Использование REST API для каталога и тегов
Базовый эндпоинт для получения списка репозиториев:
GET /v2/_catalog
Пример запроса с помощью curl к локальному реестру:
curl -X GET http://myregistry.local:5000/v2/_catalog
Ответ возвращается в JSON:
{"repositories":["myapp/backend","myapp/frontend","shared/nginx"]}
Для крупных реестров с сотнями репозиториев каталог отдаётся постранично. Запросите следующую страницу параметром n:
curl -X GET "http://myregistry.local:5000/v2/_catalog?n=100"
Когда нужен список тегов конкретного образа, используйте эндпоинт:
GET /v2/<name>/tags/list
На практике это выглядит так:
curl -X GET http://myregistry.local:5000/v2/myapp/backend/tags/list
Ответ содержит массив тегов:
{"name":"myapp/backend","tags":["1.2.3","1.2.2","1.2.1","latest"]}
Для фильтрации и форматирования вывода удобно использовать jq. Например, получить только имена репозиториев без обёртки JSON:
curl -s http://myregistry.local:5000/v2/_catalog | jq -r '.repositories[]'
Ограничения Docker Hub API жёстче, чем у собственного registry. Бесплатный план Docker Hub разрешает 100 запросов в час к каталогу, а пагинация работает с фиксированным шагом в 100 записей. Для автоматизации мониторинга лучше использовать локальный registry, где вы контролируете лимиты.
Удаление образов и тегов: полная очистка
Удаление тега командой docker rmi на клиентской машине не затрагивает реестр. Образ продолжает храниться на сервере и занимать место. Чтобы физически освободить дисковое пространство, требуется два шага: удаление манифеста через API и запуск сборщика мусора. Пропуск любого из них оставляет мусор в хранилище. В отдельной статье мы детально разобрали все сценарии очистки реестра образов - от безопасного удаления отдельных тегов до массовой зачистки устаревших данных с готовыми скриптами автоматизации.
Удаление манифеста через API
Манифест - это JSON-документ, описывающий состав образа: слои, конфигурацию, digest'ы. Удаление манифеста отвязывает тег от набора слоёв. Сначала получите digest образа по тегу. Для этого выполните HEAD-запрос и извлеките заголовок Docker-Content-Digest:
curl -I -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
http://myregistry.local:5000/v2/myapp/backend/manifests/1.2.3
В ответе найдите строку:
Docker-Content-Digest: sha256:abc123def456...
Теперь удалите манифест, передав этот digest:
curl -X DELETE http://myregistry.local:5000/v2/myapp/backend/manifests/sha256:abc123def456...
После успешного выполнения API вернёт 202 Accepted. Тег 1.2.3 исчезнет из списка тегов, но слои образа пока останутся на диске. Они будут висеть до следующего запуска garbage collection.
Запуск сборщика мусора для освобождения места
Сборщик мусора сканирует хранилище слоёв и удаляет те, на которые не ссылается ни один манифест. Команда запускается внутри контейнера registry:
docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml
Ключевой момент: перед запуском сборщика мусора реестр необходимо перевести в режим только для чтения. Если этого не сделать, параллельные push-операции могут повредить целостность данных. Добавьте в секцию maintenance конфигурационного файла registry:
maintenance:
uploadpurging:
enabled: true
age: 168h
interval: 24h
dryrun: false
readonly:
enabled: true
После завершения garbage collection верните readonly в false и перезапустите registry. Рекомендуемое расписание - раз в неделю в нерабочие часы. Процесс сканирует все слои и может нагружать дисковую подсистему, особенно на HDD-хранилищах. Если реестр обслуживает активный CI/CD, планируйте очистку на ночь с субботы на воскресенье.
Стратегии тегирования и версионирования образов
Хаос в тегах - прямой путь к неконтролируемым развёртываниям и откатам. Когда разработчик пушит образ с тегом latest, а через час другой разработчик делает то же самое, вы теряете traceability. Восстановить, какой именно код работал в момент инцидента, становится невозможно. Выбор стратегии тегирования зависит от зрелости процессов в команде.
Семантическое версионирование vs хеш коммита
Семантическое версионирование (SemVer) использует трёхкомпонентный номер: MAJOR.MINOR.PATCH. Например, 1.2.3 означает мажорную версию 1, минорное обновление 2 и патч 3. Плюсы подхода: человек сразу понимает масштаб изменений. Увидев переход с 1.2.3 на 2.0.0, администратор знает - в релизе ломающие изменения, требуется ручная проверка. Минус: SemVer требует дисциплины. Разработчики должны вручную повышать версии в соответствии с характером правок, иначе теги дезинформируют.
Хеш коммита (например, git-abc12345) генерируется автоматически в CI/CD. Плюс: полная автоматизация и однозначная связь образа с исходным кодом. Минус: по тегу невозможно определить, содержит ли образ критические изменения или мелкий багфикс. Для зрелых команд оптимальна комбинация: основной тег - SemVer, дополнительный - хеш коммита. В пайплайне это выглядит так:
docker tag app:latest registry.local/app:1.2.3
docker tag app:latest registry.local/app:git-abc12345
Образ получает два тега, указывающих на один и тот же манифест. SemVer используется для ручного контроля и коммуникации с командой, хеш - для автоматизированных систем и аудита.
Избегайте плавающих тегов в production
Тег latest - самый опасный элемент в production-окружении. Он перемещается при каждом новом push, и вы теряете контроль над тем, какая версия образа развёрнута. Представьте: Kubernetes-под перезапускается, kubelet запрашивает образ с тегом latest, а в реестре уже лежит новый билд с несовместимыми изменениями. Под стартует, приложение падает, причина неочевидна.
Фиксируйте конкретную версию в манифестах деплоймента. Вместо:
image: registry.local/myapp/backend:latest
Используйте:
image: registry.local/myapp/backend:1.2.3
Единственное допустимое применение latest - локальная разработка и песочницы, где нестабильность приемлема. Для staging и production только жёстко зафиксированные теги.
Политики очистки и предотвращение дублирования данных
Реестр, работающий без политик очистки, напоминает склад без инвентаризации. Место заканчивается внезапно, а удалить ненужное без риска для рабочих сервисов сложно. Автоматизация очистки и понимание механизмов дедупликации решают эту проблему на корню.
Дедупликация слоев: как реестр экономит место
Распространено заблуждение, что каждый тег занимает полный объём образа. На деле Docker Registry использует content-addressable storage: слои адресуются по sha256-хешу их содержимого. Если два образа используют одинаковый базовый слой (например, ubuntu:22.04), физически он хранится в единственном экземпляре. Десять тегов, собранных поверх одного базового образа, не занимают в десять раз больше места - дублируются только изменённые слои.
Проверить это можно, заглянув в структуру хранилища registry:
ls /var/lib/registry/docker/registry/v2/blobs/sha256/
Каталог содержит поддиректории, имена которых - первые два символа хеша. Внутри лежат файлы с именами-хешами. Именно эти файлы и есть слой. Манифесты лишь ссылаются на них через digest. Пока на слой ссылается хотя бы один манифест, сборщик мусора его не тронет. После удаления последнего манифеста слой становится кандидатом на удаление при следующем запуске garbage collection.
Автоматизация очистки: скрипты и политики
Ручное удаление тегов через curl не масштабируется. Для регулярной очистки нужны политики. Если вы используете Harbor, политики настраиваются в веб-интерфейсе: укажите «хранить последние 5 версий» или «удалять образы старше 30 дней». Для vanilla Docker Registry автоматизация реализуется скриптами.
Пример bash-скрипта, который оставляет только последние 5 тегов в репозитории myapp/backend:
#!/bin/bash
REPO="myapp/backend"
REGISTRY="http://myregistry.local:5000"
KEEP=5
TAGS=$(curl -s ${REGISTRY}/v2/${REPO}/tags/list | jq -r '.tags[]' | grep -v latest | sort -V)
COUNT=$(echo "$TAGS" | wc -l)
if [ $COUNT -gt $KEEP ]; then
DELETE_COUNT=$((COUNT - KEEP))
echo "$TAGS" | head -n $DELETE_COUNT | while read TAG; do
DIGEST=$(curl -sI -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
${REGISTRY}/v2/${REPO}/manifests/${TAG} | grep Docker-Content-Digest | awk '{print $2}' | tr -d '\r')
curl -X DELETE ${REGISTRY}/v2/${REPO}/manifests/${DIGEST}
echo "Deleted $REPO:$TAG"
done
fi
Скрипт сортирует теги по версиям (флаг -V в sort), пропускает latest и удаляет все, кроме последних KEEP штук. Добавьте его в cron с еженедельным запуском:
0 2 * * 0 /usr/local/bin/registry-cleanup.sh
После очистки тегов обязательно запускайте garbage collection, иначе дисковое пространство не освободится. Полный цикл автоматизации: скрипт удаляет теги по политике, переводит registry в read-only, запускает сборщик мусора, возвращает read-write. Всё это оборачивается в одну cron-задачу. Практические рекомендации по оптимизации хранения образов с цифрами экономии и готовыми конфигурациями для Harbor собраны в соответствующем руководстве.
Организация хранения и управление доступом
Когда над проектом работает несколько команд, единый плоский список репозиториев превращается в свалку. Без соглашения об именовании и разграничения прав разработчик из команды А может случайно перезаписать образ команды Б. Структура и доступы закладываются на старте - переделывать хаос всегда дороже.
Соглашения об именовании репозиториев
Хорошее имя репозитория отвечает на три вопроса: какая команда владеет, какой сервис внутри, какое окружение. Шаблон выглядит так:
<команда>/<сервис>/<окружение>
Примеры из практики:
platform/auth-service- сервис аутентификации, владелец platform teampayments/gateway/staging- платёжный шлюз на стейджинге, владелец payments teamshared/nginx- общий образ Nginx с корпоративными настройками
Иерархия отражается в пути репозитория. Это упрощает навигацию и позволяет строить автоматизацию: например, политики очистки для staging-окружений могут быть агрессивнее, чем для production.
Базовая аутентификация и авторизация
Минимальная защита приватного реестра - базовая аутентификация через htpasswd. Создайте файл паролей:
docker run --rm httpd:2 htpasswd -Bbn admin securepassword > htpasswd
В конфигурации registry укажите путь к этому файлу:
auth:
htpasswd:
realm: basic-realm
path: /auth/htpasswd
Теперь push и pull требуют логина. Для разделения прав на чтение и запись базовой аутентификации недостаточно - нужен прокси с авторизацией (Nginx с Lua) или полноценный registry с RBAC, например Harbor. В Harbor вы создаёте проекты, назначаете роли (гость, разработчик, администратор) и управляете доступом через веб-интерфейс. Интеграция с LDAP или OAuth позволяет использовать корпоративные учётные записи. Вопросы безопасности реестра - от сканирования уязвимостей до подписания образов - мы разобрали в отдельном материале по защите реестра образов.
Обеспечение стабильности и масштабируемости реестра
Реестр - критический компонент инфраструктуры. Если он недоступен, новые версии приложений не разворачиваются. Если данные потеряны, восстановить production-окружение после сбоя становится невозможно. Production-эксплуатация требует High Availability и продуманной стратегии бэкапов.
Горизонтальное масштабирование и отказоустойчивость
Vanilla Docker Registry спроектирован как stateless-приложение. Всё состояние хранится во внешнем бэкенде: файловой системе, S3-совместимом объектном хранилище или NFS. Это позволяет запустить несколько экземпляров registry за балансировщиком нагрузки. Схема проста:
- Два или более контейнеров registry на разных хостах
- Общий бэкенд - S3-бакет или NFS-шар
- Nginx или HAProxy в качестве балансировщика с проверкой здоровья
Конфигурация registry для работы с S3:
storage:
s3:
accesskey: AKIAIOSFODNN7EXAMPLE
secretkey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
region: us-east-1
bucket: my-registry-bucket
При такой архитектуре отказ одного экземпляра не прерывает работу - балансировщик направляет трафик на оставшиеся. Для геораспределённых команд можно поднять реплики в разных дата-центрах, работающие с одним S3-бакетом через региональные эндпоинты.
Резервное копирование и восстановление
Бэкапировать нужно два компонента: конфигурацию registry и содержимое бэкенда. Конфигурация - это текстовый YAML-файл, его достаточно хранить в Git. Содержимое бэкенда зависит от типа хранилища:
- Для файлового бэкенда - рекурсивное копирование каталога /var/lib/registry
- Для S3 - настройка версионирования бакета и периодическое копирование в другой регион
Если используется внешняя база данных (Harbour с PostgreSQL), добавьте в план бэкапа дамп БД. Проверяйте восстановление раз в квартал: поднимите тестовый registry из бэкапа, запросите каталог образов, скачайте один из них и запустите контейнер. Только так вы будете уверены, что процедура работает.
Мониторинг состояния реестра стройте на метриках Prometheus. Registry отдаёт метрики на эндпоинте /metrics: количество запросов, latency, объём хранилища, ошибки аутентификации. Настройте алерты на падение свободного места (менее 20%) и рост latency выше 500 мс. Это даст время на реакцию до того, как реестр станет узким горлышком CI/CD.
Реестр образов живёт в связке с пайплайнами сборки. Готовые конфигурации для автоматической публикации образов из Jenkins, GitLab CI и GitHub Actions с примерами тегирования и очистки собраны в руководстве по интеграции реестра с CI/CD.