Learning Platform
Глоссарий Troubleshooting
Урок 20.01 · 24 мин
Средний
dockerregistriesproductionghcrecrharborcosign

Приватные registry: GHCR, ECR, GAR, Harbor

Docker Hub — это публичный registry. Для prod-кода ты пушишь в приватный registry. В 2026 году выбор такой:

  • GitHub Container Registry (GHCR) — встроен в GitHub. Логин через GITHUB_TOKEN, бесплатно для public, дёшево для private.
  • Amazon ECR — AWS-native. Интеграция с IAM, лучший выбор, если ты на AWS.
  • Google Artifact Registry (GAR) — GCP-native. Заменил deprecated Container Registry.
  • Harbor — self-hosted OSS. Под контролем команды, с CVE-scanning и signing из коробки.

В этом уроке: чем они отличаются, как выбрать, и что важно знать про retention/immutability/signing для production-стека.


Зачем TLS — три гарантии безопасности и что без него

Какой registry выбрать

RegistryХостингЦена (private)Когда брать
GHCRgithub.com$0.25/GB/мес после 0.5GBЕсли код на GitHub, простой setup
ECRAWS$0.10/GB/месk8s/ECS в AWS, IAM integration
GARGCP$0.10/GB/месGKE / Cloud Run, IAM integration
ACRAzure$0.167/GB/месAKS, Azure integration
Harborself-hostedстоимость хостаХочешь полный контроль, on-prem, air-gapped

Для junior’а решение обычно простое: используй cloud-native registry своего облака. На AWS — ECR, на GCP — GAR, на Azure — ACR. Это минимизирует latency (registry и compute в одном регионе) и интегрируется с IAM/IRSA/Workload Identity.

GHCR — отличный default, если у тебя monorepo на GitHub и compute разбросан по разным местам.

Harbor подходит только командам с DevOps-ресурсом для поддержки self-hosted сервиса.

Где хранить образы: managed vs self-hosted
Managed (GHCR/ECR/GAR)zero opsCloud провайдер управляет всем: storage, replication, security patches, monitoring. Ты платишь за GB-месяц + egress. Поднимается за минуты.
vs
Self-hosted (Harbor)full controlТы деплоишь Harbor в свой k8s/VM. Полный контроль: CVE-scanning, signing, replication-policies, ACL. Минус: нужно поддерживать.
Small team in cloudСценарий: 1-5 команд, всё в одном облаке. Managed -- очевидный выбор.
Regulated / on-premСценарий: финансы/госсектор/air-gapped. Self-hosted Harbor в private k8s.

GHCR: самый простой setup

GitHub Container Registry адресуется как ghcr.io/{owner}/{repo}/{image}:{tag}. Логин:

# Создай Personal Access Token (PAT) с scope "write:packages"
# https://github.com/settings/tokens/new

echo $GHCR_TOKEN | docker login ghcr.io -u USERNAME --password-stdin

# Push
docker tag my-etl:v1 ghcr.io/USERNAME/my-repo/my-etl:v1
docker push ghcr.io/USERNAME/my-repo/my-etl:v1

В CI ещё проще — GITHUB_TOKEN создаётся автоматически:

- uses: docker/login-action@v3
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

Visibility: packages в GHCR по умолчанию private. Сделать public:

  • Repo Settings -> Packages -> найди package -> Package settings -> Visibility -> Public.

Или через CLI:

gh api --method PATCH /user/packages/container/my-etl/visibility -f visibility=public

ECR: AWS-native

# Создать repository через CLI или Terraform
aws ecr create-repository \
  --repository-name my-etl \
  --image-scanning-configuration scanOnPush=true \
  --image-tag-mutability IMMUTABLE

# Login (token expires per 12 hours)
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

# Push
docker tag my-etl:v1 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-etl:v1
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-etl:v1

Ключевые ECR-фичи:

  • scan on push — Amazon Inspector сканит CVE при каждом push. Результаты в Console.
  • image tag mutability: IMMUTABLE — нельзя перезаписать тег. Push с тем же тегом возвращает ImageAlreadyExists. Это критично для prod-deploy.
  • Lifecycle policy — auto-cleanup по правилам (“оставить 30 последних untagged”, “удалить старше 90 дней”).

В CI с OIDC (без AWS-credentials):

- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123:role/gha-ecr-push
    aws-region: us-east-1
- uses: aws-actions/amazon-ecr-login@v2
- run: |
    docker build -t my-etl:${{ github.sha }} .
    docker tag my-etl:${{ github.sha }} 123.dkr.ecr.us-east-1.amazonaws.com/my-etl:${{ github.sha }}
    docker push 123.dkr.ecr.us-east-1.amazonaws.com/my-etl:${{ github.sha }}

GAR: GCP-native

GAR заменил deprecated Container Registry. Адресуется как {region}-docker.pkg.dev/{project}/{repo}/{image}:{tag}.

gcloud artifacts repositories create my-etl-repo \
  --repository-format=docker \
  --location=us-east1 \
  --description="My ETL images"

gcloud auth configure-docker us-east1-docker.pkg.dev

docker tag my-etl:v1 us-east1-docker.pkg.dev/my-project/my-etl-repo/my-etl:v1
docker push us-east1-docker.pkg.dev/my-project/my-etl-repo/my-etl:v1

GAR features:

  • Per-repo permissions через IAM.
  • Cleanup policies — аналог ECR lifecycle.
  • Native CVE-scanning через Container Analysis API.
  • Cross-region replication для disaster recovery.

Harbor: self-hosted

Harbor — OSS-проект от CNCF (incubating). Деплоится как k8s-helm-чарт или docker compose. Фичи:

  • Robot accounts — service-аккаунты для CI без человеческих credentials.
  • CVE-scanning через встроенный Trivy.
  • Image signing через Cosign / Notary.
  • Replication между Harbor-instances и external registries.
  • Quotas per-project.

Helm-install:

helm repo add harbor https://helm.goharbor.io
helm install harbor harbor/harbor \
  --namespace harbor \
  --create-namespace \
  --set externalURL=https://harbor.internal.company.com

Push:

docker login harbor.internal.company.com -u robot$ci-push -p ROBOT_TOKEN
docker tag my-etl:v1 harbor.internal.company.com/de-team/my-etl:v1
docker push harbor.internal.company.com/de-team/my-etl:v1

Стоимость Harbor — это compute и storage в твоём cloud / on-prem + поддержка. Для маленькой команды это излишество. Для compliance-strict орг (банки, госструктуры) — стандарт.


Retention policies: автоудаление

В prod-CI registry быстро забивается: каждый push на main = новый тег. Через год — гигабайты untagged images. Retention policy автоматически чистит.

ECR пример

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Keep last 10 prod tags",
      "selection": {
        "tagStatus": "tagged",
        "tagPatternList": ["v*"],
        "countType": "imageCountMoreThan",
        "countNumber": 10
      },
      "action": {"type": "expire"}
    },
    {
      "rulePriority": 2,
      "description": "Delete untagged after 7 days",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 7
      },
      "action": {"type": "expire"}
    }
  ]
}

Применить:

aws ecr put-lifecycle-policy \
  --repository-name my-etl \
  --lifecycle-policy-text file://policy.json

GHCR пример

В GHCR retention настраивается через UI (Package settings -> Manage versions) или через REST API:

# Удалить все версии старше 90 дней, не привязанные к active deployment
gh api --method GET /user/packages/container/my-etl/versions \
  --jq '.[] | select(.created_at < (now - 90*24*60*60 | strftime("%Y-%m-%dT%H:%M:%SZ"))) | .id' | \
  xargs -I{} gh api --method DELETE /user/packages/container/my-etl/versions/{}

В GHCR с 2024 года также есть UI-policy: Settings -> Code, planning, and automation -> Packages -> Manage retention.


Immutable tags: критично для prod

Immutable tag — registry отказывается перезаписать существующий тег. Зачем:

Без immutability:

# Прод смотрит на :v1.0.0
# Кто-то делает push my-etl:v1.0.0 с broken-кодом
# Все prod-инстансы при следующем pull получат broken-код
# В docker-логах не понятно, что произошло -- digest изменился, но tag тот же

С immutability:

docker push my-etl:v1.0.0
# uploaded
docker push my-etl:v1.0.0
# Error: tag invalid: The image tag 'v1.0.0' already exists

Прод-теги никогда не перезаписываются. Чтобы пушить новый билд — нужен новый тег.

Включается в registry:

  • ECR: --image-tag-mutability IMMUTABLE при создании.
  • GHCR: package settings -> Tag immutability (с 2024).
  • GAR: --immutable-tags при создании repo.
  • Harbor: Project -> Configuration -> Tag immutability (rules per pattern, например release-*).
WARNING

ВНИМАНИЕ: immutability обычно делается для release-тегов (v1.2.3), но НЕ для rolling-тегов (latest, main, dev). На rolling-теги immutability ломает CI. Используй паттерн-правила: immutable matching v*, mutable matching main / latest.


Image signing: Cosign / Sigstore

Чтобы убедиться, что образ из registry не подменён, его подписывают. Современный стандарт — Cosign от Sigstore.

# Установка
brew install cosign  # или go install github.com/sigstore/cosign/v2/cmd/cosign@latest

# Подписать образ (keyless через OIDC)
cosign sign ghcr.io/USERNAME/my-etl:v1.0.0

# Подписать с ключом
cosign generate-key-pair
cosign sign --key cosign.key ghcr.io/USERNAME/my-etl:v1.0.0

# Верификация
cosign verify --key cosign.pub ghcr.io/USERNAME/my-etl:v1.0.0

Keyless-mode — подпись делается через OIDC-токен GitHub Actions, без хранения ключей. Это де-факто стандарт в 2026 для open-source. Подписи хранятся в Rekor (immutable transparency log).

Для prod-deploy:

  • В k8s ставится policy-controller (Kyverno, OPA Gatekeeper), который верифицирует подписи перед запуском Pod’а.
  • Образ без подписи — Pod не стартует.

Это защищает от двух атак:

  • Compromised registry: атакующий пушит зловреда вместо твоего образа. Без подписи он не пройдёт верификацию.
  • Compromised CI: атакующий получил доступ к билду. Но если подпись идёт через OIDC, в Rekor видны метаданные (“этот образ подписан pipeline на main-branch”) — можно отследить инцидент.

Pull-through cache

В CI каждый job стартует с нуля и делает docker pull python:3.11-slim, docker pull postgres:16. Это медленно (Docker Hub rate limits) и тратит сетевой трафик.

Pull-through cache — registry, который проксирует upstream-registries и кэширует.

  • ECR pull-through cache — нативная feature: ECR-repo проксирует Docker Hub / Quay / GAR. 123.dkr.ecr.us-east-1.amazonaws.com/docker-hub/python:3.11-slim — кэшированная копия.
  • Harbor proxy cache projects — то же самое self-hosted.
  • registry:2 (open-source Docker Distribution) с proxy config — простой DIY-вариант.

Для DE-команд это снижает CI build time на 30-50% и обходит anonymous rate limit Docker Hub (100 pull/6h).


Попробуй сам

# 1. Создай GitHub PAT с write:packages
# https://github.com/settings/tokens

# 2. Логин в GHCR
echo $TOKEN | docker login ghcr.io -u $USER --password-stdin

# 3. Build & push простого образа
docker pull alpine:3.21
docker tag alpine:3.21 ghcr.io/$USER/test-registry:v1
docker push ghcr.io/$USER/test-registry:v1

# 4. Проверь на github.com -> твой профиль -> Packages

# 5. Pull откуда-то ещё
docker logout ghcr.io
docker pull ghcr.io/$USER/test-registry:v1
# Если public -- работает без логина
# Если private -- 403

# 6. Cosign-подпись (keyless, требует OIDC)
brew install cosign
cosign sign ghcr.io/$USER/test-registry:v1
# Откроется browser для GitHub OAuth, подпись будет сохранена

cosign verify \
  --certificate-identity=YOUR_GITHUB_EMAIL \
  --certificate-oidc-issuer=https://github.com/login/oauth \
  ghcr.io/$USER/test-registry:v1

# 7. Cleanup
docker rmi ghcr.io/$USER/test-registry:v1

Проверка знанийKnowledge check
Ваша команда деплоит ETL-image в k8s через :latest tag. Раз в неделю случайно ломается prod из-за того, что кто-то запушил broken build в latest. Как решить с минимальными изменениями?
ОтветAnswer
Перейти на immutable tags + sha-теги для deploy. (1) В registry включить tag immutability (ECR: --image-tag-mutability IMMUTABLE, или Harbor immutability rule на pattern v*). (2) В CI генерировать тег по SHA коммита: my-etl:sha-abc1234. (3) В k8s manifests использовать :sha-abc1234 вместо :latest (через kustomize/Helm variable). (4) Bonus: верификация подписи Cosign в admission controller -- образ без подписи не стартует. Результат: latest можно оставить для dev-стендов, но prod всегда привязан к конкретному, неизменяемому SHA-тегу. Подмена невозможна -- если кто-то перепушит :sha-abc1234, push отвергнут.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Команда DE деплоит на AWS. Какой registry будет наиболее логичен с точки зрения cost и integration?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4