Анатомия имени образа:
Полное имя выглядит так: адрес-реестра[:порт]/пространство/репозиторий:тег. Когда вы пишете docker pull nginx, движок молча разворачивает это в docker.io/library/nginx:latest. Реестр зашит прямо в имя, отдельного параметра "куда пушить" у docker push нет. Отсюда практический вывод: чтобы залить образ в свой реестр, его сначала нужно перетегировать.
Код: Выделить всё
docker tag myapp:1.4.2 cr.yandex/crp1abc2def3/myapp:1.4.2
echo "$CR_TOKEN" | docker login cr.yandex -u oauth --password-stdin
docker push cr.yandex/crp1abc2def3/myapp:1.4.2
docker pull cr.yandex/crp1abc2def3/myapp:1.4.2
Варианты приватных реестров на 2026 год: GitLab Container Registry и ghcr.io идут бесплатным довеском к репозиториям, Harbor ставят как корпоративный реестр со сканированием и RBAC, в облаках СНГ есть Yandex Container Registry, реестры Selectel и VK Cloud.
Свой registry за вечер:
Для внутренних нужд хватает образа registry:3 (проект CNCF Distribution). Поднимем его с базовой аутентификацией:
Код: Выделить всё
mkdir -p auth
docker run --rm httpd:2.4 htpasswd -Bbn builder 'S3cret-2026' > auth/htpasswd
docker run -d --name registry --restart=always -p 5000:5000 \
-v registry-data:/var/lib/registry \
-v "$PWD/auth:/auth:ro" \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM=Registry \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:3
Нюанс этого реестра: удаление образа через API не освобождает диск. Слои чистит отдельная команда garbage-collect внутри контейнера, и гонять ее нужно периодически, иначе том распухнет.
Теги против digest:
Тег подвижен. Сегодня myapp:1.4.2 указывает на один образ, завтра кто-то пересоберет и перезальет под тем же тегом другой. Digest, наоборот, неизменен: это sha256 от манифеста образа, и pull по digest всегда вернет байт в байт то же самое.
Код: Выделить всё
docker buildx imagetools inspect cr.yandex/crp1abc2def3/myapp:1.4.2
docker pull cr.yandex/crp1abc2def3/myapp@sha256:9f2c4e1a...
imagePullSecrets:
Kubernetes тянет образы сам, силами kubelet на каждой ноде, и ваш docker login ему ничего не говорит. Учетку для реестра кладут в секрет типа docker-registry:
Код: Выделить всё
kubectl create secret docker-registry cr-creds \
--docker-server=cr.yandex \
--docker-username=oauth \
--docker-password="$CR_TOKEN" \
-n staging
Код: Выделить всё
spec:
imagePullSecrets:
- name: cr-creds
containers:
- name: app
image: cr.yandex/crp1abc2def3/myapp@sha256:9f2c4e1a...
Типичные грабли:
Ошибка denied: requested access to the resource is denied при push почти всегда означает, что вы забыли docker tag с адресом реестра и докер ломится в Docker Hub под чужим именем. Права тут ни при чем.
Перезаливка существующего тега плюс кэш на нодах. Политика imagePullPolicy по умолчанию IfNotPresent (Always она только для latest и образов без тега). Нода, у которой образ уже есть, обновление просто не заметит, и вы получите кластер, где половина подов на старой версии. Лечится уникальными тегами или digest, а не перезаписью.
Лимиты Docker Hub на анонимные pull регулярно кладут CI: десятки джоб с одного NAT-адреса быстро упираются в потолок. Решение: аутентифицироваться в CI или поднять pull-through кэш, тот же registry:3 умеет работать прокси-зеркалом.
И не храните долгоживущие пароли реестра в переменных CI, если платформа умеет выдавать токен на время джобы: в GitLab это CI_JOB_TOKEN, у облаков IAM-токены с коротким сроком жизни.
Итог:
Реестр зашит в имя образа, тег подвижен, digest вечен. В прод деплоим по digest или неперезаписываемым тегам, Kubernetes ходит в приватный реестр через imagePullSecrets, а свой registry поднимается за вечер, но требует TLS и уборки мусора. В следующей главе берем BuildKit и buildx: multi-arch сборки, секреты сборки и экспорт кэша лягут как раз поверх того, что настроили сегодня.