Container Registry
A service that stores and distributes Docker images. The registry serves as the central repository from which developers push built images and deployment systems pull them to create containers.
What a container registry is
A container registry stores and distributes Docker images, much like npm does for JavaScript packages or PyPI for Python packages. You build an image once, push it to a registry, and every server or cluster that needs it pulls it from the same place.
docker build"] --> B["Container registry
Docker Hub, ECR, Harbor"] B --> C["Production server
docker pull"] B --> D["Kubernetes node
image pull"] class A,C,D base class B key classDef base fill:#f8fafc,stroke:#94a3b8,stroke-width:1.5px,color:#0f172a classDef key fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#0f172a
Common registries
- Docker Hub: the default registry for
docker pull. Public images are free, and pull rate limits apply. - GitHub Container Registry (ghcr.io): integrates with GitHub Actions and permissions.
- AWS ECR: private registry with IAM-based access, a good fit on AWS.
- Google Artifact Registry: the current registry service on GCP, replacing Container Registry (gcr.io).
- Azure Container Registry (ACR): the equivalent on Azure.
- Harbor: open-source, self-hosted, with vulnerability scanning built in.
Image names
[registry-hostname/][namespace/]repository[:tag][@digest] nginx:1.25 # docker.io/library/nginx:1.25ghcr.io/myorg/payment-api:v3.1.0 # GitHub Container Registry123456789012.dkr.ecr.ap-south-1.amazonaws.com/payment-api:v3.1.0 # AWS ECRregistry.example.com/payment-api:v3.1.0 # Self-hostedPushing and pulling
# Log in (credentials are stored in ~/.docker/config.json)docker login registry.example.com # AWS ECR uses short-lived tokensaws ecr get-login-password --region ap-south-1 | \ docker login --username AWS --password-stdin \ 123456789012.dkr.ecr.ap-south-1.amazonaws.com # Tag, push, pulldocker tag payment-api:v3.1.0 registry.example.com/payment-api:v3.1.0docker push registry.example.com/payment-api:v3.1.0docker pull registry.example.com/payment-api:v3.1.0Rate limits and cleanup
Docker Hub limits how many images can be pulled per hour, with lower limits for anonymous users than for logged-in ones. Shared CI runners often hit these limits. Check Docker's current limits, then either authenticate in CI or pull through a mirror or cache.
On ECR, old images accumulate and cost money. A lifecycle policy removes them automatically:
{ "rules": [ { "rulePriority": 1, "description": "Keep the last 10 tagged images", "selection": { "tagStatus": "tagged", "tagPrefixList": ["v"], "countType": "imageCountMoreThan", "countNumber": 10 }, "action": { "type": "expire" } } ]}aws ecr put-lifecycle-policy \ --repository-name payment-api \ --lifecycle-policy-text file://lifecycle-policy.json \ --region ap-south-1Troubleshooting
| Error | Cause | Fix |
|---|---|---|
pull access denied |
Not authenticated or no permission | docker login to the registry |
authentication required on ECR |
Token expired (about 12 hours) | Re-run aws ecr get-login-password |
toomanyrequests |
Docker Hub rate limit | Authenticate or use a mirror |
manifest unknown |
Tag does not exist | Check the exact tag in the registry |
Common MistakeDeploying
:latest. The tag is mutable, so a redeploy can silently pull different code than what is currently running. Use an immutable tag such as a version number or git SHA.
SecurityUse private repositories for production images. If a secret is ever baked into a pushed image, rotate it immediately, because deleting the image does not undo earlier pulls.
Frequently Asked Questions
Why do teams run a private container registry instead of only using Docker Hub?
Docker Hub's free tier enforces pull-rate limits that can throttle a CI pipeline pulling the same base image hundreds of times a day. Private registries (ECR, Artifact Registry, GitHub Container Registry, or self-hosted Harbor) avoid those limits, keep proprietary images out of public view, and typically integrate with the cloud provider's IAM for pull authentication instead of requiring separate registry credentials.
What's a common container registry mistake in production?
Tagging and deploying images as `:latest` instead of an immutable tag like a git SHA or semantic version. `:latest` is mutable, so a rollback or a redeploy on another node can silently pull a different image than what is currently running, breaking the assumption that the same tag means the same bits. Always deploy pinned, immutable tags, and reserve `:latest` (if used at all) for local development.