Реестры образов: Docker Hub, GHCR, ECR, Harbor
Registry это сервис, который хранит образы и раздаёт их по HTTP API. До 2020 года выбор был один — Docker Hub. После того как Docker Inc ввела жёсткие rate limits на anonymous pulls, DE-команды массово начали переезжать на альтернативы: GitHub Container Registry (GHCR), AWS Elastic Container Registry (ECR), Harbor для self-hosted. Каждый со своими trade-off.
В этом уроке разберёмся, что выбрать для своих проектов, как читать image-name с учётом разных registries и почему понимание rate limits спасает CI от mysterious failures.
Что такое registry технически
Под капотом registry это веб-сервис, реализующий OCI Distribution Specification (бывший Docker Registry HTTP API V2). Это набор HTTP-эндпоинтов:
GET /v2/<name>/manifests/<reference>— получить манифест по тегу или digest’уGET /v2/<name>/blobs/<digest>— скачать слой (blob)POST /v2/<name>/blobs/uploads/— начать загрузку нового blob’аPUT /v2/<name>/manifests/<tag>— зарегистрировать манифест под тегомGET /v2/_catalog— список репозиториев (опционально, не все реализуют)GET /v2/<name>/tags/list— список тегов в репозитории
Любой сервис, реализующий эту спецификацию, является OCI registry. Docker Hub, GHCR, ECR, Harbor, GitLab Container Registry, Artifactory, Quay, Sonatype Nexus, локальный registry:2 от Docker — все говорят на одном языке. Это позволяет переключаться между ними без изменения CLI: docker pull ghcr.io/user/image:tag работает так же как docker pull image:tag (где skipped prefix == docker.io).
Когда ты пишешь docker pull postgres:16, Docker подразумевает docker.io/library/postgres:16. Префикс docker.io/ это Docker Hub, library/ это official-images namespace. Для образов от других пользователей: docker pull bitnami/redis:7 означает docker.io/bitnami/redis:7. Для других registries префикс надо указывать явно: ghcr.io/apache/airflow:2.10.
Анатомия HTTP-запроса — что внутри request и response
Docker Hub: первоисточник и rate limits
Docker Hub (hub.docker.com, registry docker.io) это самый старый и большой публичный registry. Здесь живут official images (postgres, redis, python, node, ubuntu) и миллионы community-образов. Бесплатный для public images.
С ноября 2020 Docker Inc ввела rate limits на anonymous pulls:
- Anonymous (без логина): 100 image pulls / 6 часов / IP
- Authenticated free account: 200 pulls / 6 часов
- Docker Pro: 5000 pulls / day
- Docker Team / Business: unlimited
При превышении API возвращает 429 Too Many Requests. Это критично для CI: один билд с python:3.13-slim, postgres:16, redis:7, node:22 уже 4 pull’а. При параллельных билдах на shared CI-runner с общим IP лимит выгорает за минуты.
toomanyrequests: You have reached your pull rate limit.
You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit
Что делают команды:
- Перенос своих образов в GHCR/ECR — там нет rate limits для своего же контента
- Pull-through cache — локальный registry-proxy, который кэширует ответы Docker Hub, и в CI ходят туда (см. urpose
nexusилиharborниже) - Authenticated pulls с команда-аккаунта —
docker loginв CI с paid Docker Hub аккаунтом, лимит 5000/day
GitHub Container Registry (GHCR)
GHCR (ghcr.io) запустился в 2020. Бесплатный для public images, нет rate limits для аутентифицированных pull’ов, привязан к GitHub-аккаунту/организации.
Адресация: ghcr.io/<owner>/<image>:<tag>. Например ghcr.io/apache/airflow:2.10.4.
Аутентификация через GitHub Personal Access Token (PAT) или внутри GitHub Actions через GITHUB_TOKEN:
echo $GITHUB_TOKEN | docker login ghcr.io -u $GITHUB_USER --password-stdin
# Push образа
docker tag myapp:v1 ghcr.io/myorg/myapp:v1
docker push ghcr.io/myorg/myapp:v1
В GitHub Actions это вообще одна строка:
# .github/workflows/build.yml
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
Plus GHCR: бесплатный, без rate limits, интеграция с GitHub permissions (можно ограничить pull для private images только для определённых repo). Minus: нельзя хостить official images типа postgres — это для своих сборок и форков.
AWS Elastic Container Registry (ECR)
ECR (<account>.dkr.ecr.<region>.amazonaws.com) — managed registry для AWS workloads. Платный (плата за storage + data transfer наружу), но если ты в AWS — то data transfer в тот же regional аккаунт бесплатен.
Адресация: 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:v1.
Особенности:
- IAM-based аутентификация. Не пароли, а IAM-roles. В CI это
aws ecr get-login-password | docker login. - Lifecycle policies. Можно настроить автоудаление старых тегов / untagged образов.
- ECR Public Gallery (
public.ecr.aws) — бесплатные public images типаpublic.ecr.aws/docker/library/postgres(зеркало Docker Hub без rate limits). Полезно для anonymous pulls в CI.
# Auth (в CI обычно через GitHub Actions aws-actions/configure-aws-credentials)
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
# Tag и push
docker tag myapp:v1 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:v1
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:v1
Естественный выбор для команд с EKS / ECS / Fargate. Для on-premise это просто дорого и неудобно.
Harbor: self-hosted enterprise registry
Harbor это OSS-registry, разработанный VMware (теперь под CNCF). Запускается в k8s или standalone в Docker, даёт полный контроль над хранилищем, доступом, security policies.
Что Harbor умеет помимо обычного registry:
- Vulnerability scanning — встроенный Trivy или Clair, сканирует образы при push и в фоне
- Image signing — Notary v2 / Cosign integration
- Replication — двусторонняя репликация с Docker Hub / другими Harbor
- RBAC — projects + roles (admin/maintainer/developer/guest)
- Quotas — лимит на storage / image count per project
- Audit log — кто и когда что push’ил
Используется в enterprise, где compliance требует self-hosted решения, либо в air-gapped environments (без интернет-доступа). Развернуть Harbor проще, чем кажется:
# Скачать installer
wget https://github.com/goharbor/harbor/releases/download/v2.13.0/harbor-online-installer-v2.13.0.tgz
tar xzf harbor-online-installer-v2.13.0.tgz
cd harbor
cp harbor.yml.tmpl harbor.yml
# Отредактировать hostname, https-сертификат, admin-password
nano harbor.yml
# Запустить
./install.sh
# Откроется UI на https://harbor.example.com
После установки Harbor работает как OCI-registry: docker login harbor.example.com, docker push harbor.example.com/myproject/myimage:v1.
Сравнение: что выбрать
Типичный DE-stack 2026:
- Свои образы: GHCR (если на GitHub) или ECR (если на AWS)
- Источник официальных образов: ECR Public Gallery или pull-through cache в Harbor
- CI без rate-limit headache: anonymous pull через GHCR-зеркало или ECR Public
Почему DE-команды переезжают с Docker Hub
Конкретные причины:
1. Rate limits ломают CI. Команда из 10 человек, 50 коммитов в день, каждый билд тянет 4 образа = 200 pull’ов с одного NAT-IP офиса. 429 errors каждый день.
2. Docker Hub Pro/Team дорогой для крупных команд. 500/mo только за registry.
3. Compliance. Регуляторика часто требует self-hosted images (банки, healthcare). Docker Hub за пределами компании это data exfiltration risk.
4. Скорость pull. В US/EU Docker Hub быстрый, в России / Азии / South America — медленный (зависит от CDN). Свой registry в том же регионе кратно быстрее.
5. Удобство приватных образов. На Docker Hub приватные репо — paid feature. На GHCR private repos бесплатно.
Универсальный паттерн для команд: для своих образов — GHCR/ECR, для официальных образов — pull-through cache или ECR Public Gallery. В docker-compose.yml для прода:
services:
app:
image: ghcr.io/myorg/data-pipeline@sha256:abc...
postgres:
image: public.ecr.aws/docker/library/postgres@sha256:def... # без rate-limitПопробуй сам
Посмотри на разные registries:
# Pull с Docker Hub (default)
docker pull alpine:3.21
# Pull с ECR Public (зеркало без rate limits)
docker pull public.ecr.aws/docker/library/alpine:3.21
# Сравни image IDs -- это БИТ-В-БИТ один и тот же образ
docker images | grep alpine
# Pull с GHCR (если есть public image)
docker pull ghcr.io/oven/bun:latest
# Inspect где живёт образ
docker inspect alpine:3.21 --format '{{.RepoTags}}'
docker inspect public.ecr.aws/docker/library/alpine:3.21 --format '{{.RepoTags}}'
Заметишь, что image ID (sha256) идентичен — потому что слои content-addressable, и любой registry, хостящий тот же образ, отдаст те же байты.