Skip to main content

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.

%%{init: {'themeVariables': {'fontSize': '18px'}, 'flowchart': {'nodeSpacing': 40, 'rankSpacing': 45, 'padding': 14}}}%% flowchart TD A["Developer or CI
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

Bash
[registry-hostname/][namespace/]repository[:tag][@digest]
nginx:1.25 # docker.io/library/nginx:1.25
ghcr.io/myorg/payment-api:v3.1.0 # GitHub Container Registry
123456789012.dkr.ecr.ap-south-1.amazonaws.com/payment-api:v3.1.0 # AWS ECR
registry.example.com/payment-api:v3.1.0 # Self-hosted

Pushing and pulling

Bash
# Log in (credentials are stored in ~/.docker/config.json)
docker login registry.example.com
# AWS ECR uses short-lived tokens
aws ecr get-login-password --region ap-south-1 | \
docker login --username AWS --password-stdin \
123456789012.dkr.ecr.ap-south-1.amazonaws.com
# Tag, push, pull
docker tag payment-api:v3.1.0 registry.example.com/payment-api:v3.1.0
docker push registry.example.com/payment-api:v3.1.0
docker pull registry.example.com/payment-api:v3.1.0

Rate 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:

JSON
{
"rules": [
{
"rulePriority": 1,
"description": "Keep the last 10 tagged images",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["v"],
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": { "type": "expire" }
}
]
}
Bash
aws ecr put-lifecycle-policy \
--repository-name payment-api \
--lifecycle-policy-text file://lifecycle-policy.json \
--region ap-south-1

Troubleshooting

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 Mistake

Deploying :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.

Security

Use 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.