Docker Registry Explained: Docker Hub, Private Registry & Amazon ECR
Learn how Docker registries work, Docker Hub pull limits, how to run a secure private registry, and how to push, secure and clean up images in Amazon ECR.
Docker Registry Explained: Docker Hub, Private Registries, and Amazon ECR
Building a Docker image is only half the job. To run that image on a server, in a CI pipeline or in a Kubernetes cluster, it has to be stored somewhere every machine can reach. That place is a container registry.
Registries look simple from the outside, since you just docker push and docker pull, but the choices behind them matter a great deal in production. Where you store images affects deployment speed, security, cost and reliability. Pull limits on Docker Hub can break a CI pipeline, a self-hosted registry without TLS can expose your images, and an Amazon ECR repository without lifecycle rules quietly accumulates thousands of old images.
This guide explains how registries work, then covers the three options most teams use: Docker Hub, self-hosted private registries (the open-source Distribution registry and Harbor), and Amazon Elastic Container Registry (ECR). You will learn the commands, the security settings and the best practices for each. Details are current as of October 2026 for Docker Engine 29.
Quick Answer
What Is a Docker Registry?
A registry is a server that stores container images and serves them over HTTPS using a standard API, the OCI Distribution Specification. Every registry, from Docker Hub to Amazon ECR, speaks the same protocol, which is why the same docker push and docker pull commands work everywhere.
Figure 1: How a container registry stores, receives and serves images
Registry, Repository, Tag and Digest
These four terms are often mixed up. Here is how they relate:
How Images Are Stored Inside a Registry
A registry does not store an image as one big file. It stores:
Blobs: the compressed filesystem layers and the image configuration, each addressed by its SHA-256 digest.
Manifests: small JSON documents listing the configuration blob and layer blobs that make up one image for one platform.
Indexes (manifest lists): for multi-platform images, a list of manifests, one per platform such as linux/amd64 and linux/arm64.
Tags: pointers from a name such as 1.4.0 to a manifest or index.
Because layers are content-addressed, a registry stores each unique layer once, even if hundreds of images use it. When you push, Docker first asks which layers the registry already has and uploads only the missing ones, then uploads the manifest. When you pull, Docker downloads the index, selects the manifest for your platform, and fetches only the layers you do not already have locally.
How Docker Resolves an Image Name
[registry/][namespace/]repository[:tag][@digest]
nginx -> docker.io/library/nginx:latest
bitcodematrix/api:1.4.0 -> docker.io/bitcodematrix/api:1.4.0
ghcr.io/acme/web:2.1 -> GitHub Container Registry
registry.example.com:5000/api -> a self-hosted registry on port 5000
If the first part of the name contains a dot (.) or a colon (:), or is localhost, Docker treats it as a registry host name. Otherwise it assumes Docker Hub (docker.io), and for single-word names the library namespace used by Docker Official Images. The tag defaults to latest.
Part 1: Docker Hub
Docker Hub is the world's largest container registry and the default registry for every Docker installation. It hosts several categories of trusted content alongside community images:
Security: Treat community images with caution. Prefer Official Images, Verified Publisher or Hardened Images, read the Dockerfile where available, and scan every image before using it in production.
Docker Hub Pull Limits
Docker Hub limits how many image pulls each user can make in a 6-hour window. As of October 2026, the limits are:
A pull is counted for each manifest download; a multi-platform image counts once for each platform pulled. When you exceed the limit, Docker Hub returns HTTP 429 with the error toomanyrequests. Docker revises these limits from time to time, so check the "Docker Hub pull usage and limits" page in Docker's documentation before planning capacity.
Check your remaining pulls without using one (a HEAD request does not count):
TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl -s --head -H "Authorization: Bearer $TOKEN" \
https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | grep -i ratelimit
The ratelimit-limit and ratelimit-remaining headers show your allowance and what is left, for example 100;w=21600 (100 pulls per 21,600 seconds).
How to avoid hitting the limits:
Always log in, especially on CI runners and servers. Many CI systems share one public IP address between thousands of users, so anonymous pulls run out quickly.
Cache base images with a pull-through cache, such as a self-hosted registry mirror or an Amazon ECR pull-through cache rule (both covered below).
Reuse local images. Use --pull missing (the default) instead of --pull always where freshness is not essential.
Push an Image to Docker Hub
Create an access token in Docker Hub (Account settings, then Personal access tokens) and use it instead of your password. Then log in, tag and push:
echo "$DOCKERHUB_TOKEN" | docker login -u <username> --password-stdin
docker build -t <username>/api:1.4.0 .
docker push <username>/api:1.4.0
Repositories on Docker Hub are created automatically on the first push and are public by default. Change visibility in the repository settings if the image must stay private.
Part 2: Private and Self-Hosted Registries
Teams use private registries to keep proprietary images away from the public internet, control access, keep images close to where they run, and avoid public pull limits. You can use a managed service or run your own.
Run a Basic Registry With the Distribution Image
The official registry image runs the CNCF Distribution registry. Version 3 is the current major release (registry:3); its default configuration file is /etc/distribution/config.yml. For a quick local test:
docker run -d --name registry -p 5000:5000 --restart unless-stopped \
-v registry-data:/var/lib/registry registry:3
docker tag nginx:1.29 localhost:5000/nginx:1.29
docker push localhost:5000/nginx:1.29
docker pull localhost:5000/nginx:1.29
Docker allows plain HTTP only for registries on localhost (more precisely, the 127.0.0.0/8 loopback range). For any other host, the registry must use HTTPS.
Do not disable TLS: You may see guides that add a host to insecure-registries in daemon.json to allow plain HTTP. This sends images and credentials unencrypted and allows tampering in transit. Use it only in isolated test labs, never in production.
Secure the Registry With TLS and Authentication
A production-ready minimal setup needs a TLS certificate and authentication. Create a password file with the htpasswd tool (borrowed here from the official httpd image, because the registry image does not include it):
mkdir -p auth certs
docker run --rm --entrypoint htpasswd httpd:2 -Bbn admin "Change-This-Passw0rd" > auth/htpasswd
Place your certificate and private key in certs/ as domain.crt and domain.key (for example from Let's Encrypt or your internal certificate authority), then start the registry:
docker run -d --name registry --restart unless-stopped -p 443:443 \
-v registry-data:/var/lib/registry \
-v "$(pwd)/auth":/auth:ro -v "$(pwd)/certs":/certs:ro \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:3
docker login registry.example.com
Any setting in the registry's configuration file can be overridden with an environment variable named REGISTRY_ followed by the configuration path in upper case, joined by underscores, as shown above.
If you use a certificate signed by a private certificate authority, give every Docker host that pulls from the registry a copy of the CA certificate:
sudo mkdir -p /etc/docker/certs.d/registry.example.com
sudo cp ca.crt /etc/docker/certs.d/registry.example.com/ca.crt
Store Registry Data in Amazon S3 or Another Object Store
A single server's disk is a single point of failure. The registry can store blobs in object storage instead. For example, with Amazon S3:
-e REGISTRY_STORAGE=s3 \
-e REGISTRY_STORAGE_S3_REGION=ap-south-1 \
-e REGISTRY_STORAGE_S3_BUCKET=my-registry-bucket
Grant access with an IAM instance role on the server rather than access keys. Other supported back ends include Azure Blob Storage, Google Cloud Storage and the local filesystem.
Delete Images and Reclaim Space
Deleting is disabled by default. Enable it with REGISTRY_STORAGE_DELETE_ENABLED=true, delete manifests through the registry API, and then run garbage collection to remove layers no longer referenced by any manifest:
docker exec registry registry garbage-collect /etc/distribution/config.yml
Warning: Do not run garbage collection while images are being pushed; it can delete layers of an upload in progress. Stop pushes first, or put the registry into read-only mode during maintenance. This manual effort is one reason many teams choose Harbor or a managed registry, which provide retention policies and scheduled clean-up.
Use a Registry as a Docker Hub Pull-Through Cache
A pull-through cache (mirror) fetches images from Docker Hub once, stores them, and serves later requests locally. This saves bandwidth and avoids Docker Hub pull limits across many machines. Configure the Distribution registry as a proxy:
docker run -d --name hub-mirror -p 5000:5000 --restart unless-stopped \
-v mirror-data:/var/lib/registry \
-e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
-e REGISTRY_PROXY_USERNAME=<dockerhub-user> \
-e REGISTRY_PROXY_PASSWORD=<dockerhub-access-token> \
registry:3
Then point each Docker host at the mirror in /etc/docker/daemon.json and restart Docker:
{
"registry-mirrors": ["https://mirror.example.com"]
}
registry-mirrors applies only to images from Docker Hub. Put the mirror behind TLS for any non-local access, as described above.
When to Choose Harbor Instead
The Distribution registry is deliberately minimal. Choose Harbor when you need multiple teams and projects, a web interface, role-based access control with LDAP or OIDC single sign-on, vulnerability scanning with Trivy, policies that block unscanned or unsigned images, replication between registries, quotas and retention policies. Harbor is a graduated CNCF project and is installed with Docker Compose or a Helm chart.
Part 3: Amazon Elastic Container Registry (ECR)
Amazon ECR is AWS's fully managed container registry. It is the natural choice when your containers run on Amazon ECS, AWS Fargate, Amazon EKS, AWS Lambda or EC2, because access is controlled by AWS IAM and images are pulled over the AWS network.
Key ECR Concepts
Step 1: Create a Repository
With the AWS CLI configured, create a repository with vulnerability scanning on push and immutable tags (replace the Region with your own):
aws ecr create-repository \
--repository-name bitcodematrix/api \
--image-scanning-configuration scanOnPush=true \
--image-tag-mutability IMMUTABLE \
--region ap-south-1
Traditionally the repository must exist before you push. Since December 2025, ECR can also create repositories automatically on push, using settings from a repository creation template; set one up if your pipelines push to many repositories.
Step 2: Log In, Tag and Push
ACCOUNT_ID=123456789012
REGION=ap-south-1
REGISTRY=$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com
aws ecr get-login-password --region $REGION | \
docker login --username AWS --password-stdin $REGISTRY
docker build -t $REGISTRY/bitcodematrix/api:1.4.0 .
docker push $REGISTRY/bitcodematrix/api:1.4.0
The username is always the literal value AWS. The IAM identity running these commands needs ecr:GetAuthorizationToken plus push permissions on the repository, such as ecr:InitiateLayerUpload, ecr:UploadLayerPart, ecr:CompleteLayerUpload, ecr:BatchCheckLayerAvailability and ecr:PutImage.
To avoid logging in every 12 hours on developer machines and build servers, install the Amazon ECR Docker Credential Helper and configure it in ~/.docker/config.json. Docker then fetches fresh tokens automatically using your AWS credentials:
{
"credHelpers": {
"123456789012.dkr.ecr.ap-south-1.amazonaws.com": "ecr-login"
}
}
Step 3: Pull Images on AWS Without Passwords
Workloads running on AWS should never store registry passwords. They use IAM roles instead:
Step 4: Clean Up Automatically With Lifecycle Policies
Every CI build pushes a new image, and storage is billed per GB per month. Lifecycle policies keep repositories tidy. This example expires untagged images after 7 days and keeps only the 50 most recent images overall:
{
"rules": [
{
"rulePriority": 1,
"description": "Expire untagged images after 7 days",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 7
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "Keep only the 50 most recent images",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 50
},
"action": { "type": "expire" }
}
]
}
aws ecr put-lifecycle-policy --repository-name bitcodematrix/api \
--lifecycle-policy-text file://lifecycle.json --region ap-south-1
A rule with tagStatus set to any must have the highest rulePriority number, so it is evaluated last. Preview the effect safely with aws ecr start-lifecycle-policy-preview before applying a policy to an important repository.
Step 5: Scan Images for Vulnerabilities
Configure scanning at the registry level so that every repository is covered, then view results in the console or with:
aws ecr describe-image-scan-findings --repository-name bitcodematrix/api \
--image-id imageTag=1.4.0 --region ap-south-1
Step 6: Protect Tags With Immutability
With IMMUTABLE tag mutability, pushing a tag that already exists fails with ImageTagAlreadyExistsException, so 1.4.0 can never silently change. If you also publish a moving tag such as latest, use exclusion filters so that only those tags stay mutable:
aws ecr put-image-tag-mutability --repository-name bitcodematrix/api \
--image-tag-mutability IMMUTABLE_WITH_EXCLUSION \
--image-tag-mutability-exclusion-filters filterType=WILDCARD,filter=latest \
--region ap-south-1
Step 7: Cache Public Images With Pull-Through Cache
An ECR pull-through cache rule copies images from an upstream registry into your private registry on first use and keeps them updated. Supported upstreams include Amazon ECR Public, the Kubernetes registry and Quay (no credentials needed), and Docker Hub, GitHub Container Registry, GitLab, Azure Container Registry and Chainguard (credentials stored in AWS Secrets Manager). Cross-account ECR upstreams use an IAM role instead.
For Docker Hub, first store your Docker Hub username and access token in a Secrets Manager secret whose name starts with ecr-pullthroughcache/, in the same account and Region. Then create the rule:
aws ecr create-pull-through-cache-rule \
--ecr-repository-prefix docker-hub \
--upstream-registry-url registry-1.docker.io \
--credential-arn arn:aws:secretsmanager:ap-south-1:123456789012:secret:ecr-pullthroughcache/docker-hub-AbCdEf \
--region ap-south-1
docker pull 123456789012.dkr.ecr.ap-south-1.amazonaws.com/docker-hub/library/nginx:1.29
Note the library/ part for Docker Official Images. Your workloads now pull public base images from ECR inside AWS, avoiding Docker Hub limits and internet egress from private subnets.
Other Useful ECR Features
Replication: copy images automatically to other Regions or accounts for disaster recovery and low-latency pulls.
Encryption: images are encrypted at rest with Amazon S3-managed keys by default, or with your own AWS KMS key chosen when the repository is created.
VPC endpoints: pull images privately from subnets without internet access using interface endpoints for ecr.api and ecr.dkr, plus an S3 gateway endpoint for layer downloads.
Signing: sign images with AWS Signer and Notation, or with Sigstore Cosign, and verify signatures before deployment.
ECR charges for storage per GB-month and for data transferred out of the Region. Check the Amazon ECR pricing page for current rates in your Region.
Docker Hub vs Self-Hosted Registry vs Amazon ECR
Rules of thumb: use Docker Hub for public images and as a source of trusted base images; use Amazon ECR for private images that run on AWS; use Harbor or another self-hosted registry for on-premises, air-gapped or multi-cloud environments where you need full control.
Registry Best Practices for Production
Never deploy `latest`. Use version tags such as 1.4.0 (often combined with the Git commit SHA), and deploy by digest when you need guaranteed reproducibility.
Make release tags immutable, so a tag always refers to the same image.
Scan every image on push and continuously, and block deployment of images with critical vulnerabilities.
Sign images with Cosign or Notation and verify signatures in your deployment pipeline or admission controller.
Use least privilege. CI pipelines need push access to their own repositories only; runtime environments need pull access only.
Avoid long-lived passwords. Use access tokens, IAM roles, credential helpers and OIDC federation from CI systems; always log in with --password-stdin.
Protect stored credentials. Without a credential helper, docker login stores credentials only base64-encoded in ~/.docker/config.json. Use the credential store of Docker Desktop, pass or the system keychain.
Cache external images through a pull-through cache to improve speed and resilience against upstream outages and limits.
Clean up automatically with lifecycle or retention policies.
Keep the registry close to the workload, in the same cloud Region, to reduce pull time and transfer costs.
Example: Build and Push to ECR From a CI Pipeline
The same steps work in GitHub Actions, GitLab CI, Jenkins or AWS CodeBuild. The pipeline should obtain temporary AWS credentials through OIDC federation or an IAM role rather than stored access keys:
#!/usr/bin/env bash
set -euo pipefail
REGISTRY=123456789012.dkr.ecr.ap-south-1.amazonaws.com
IMAGE=$REGISTRY/bitcodematrix/api
VERSION=1.4.0
SHA=$(git rev-parse --short HEAD)
aws ecr get-login-password --region ap-south-1 | \
docker login --username AWS --password-stdin $REGISTRY
docker buildx build --platform linux/amd64,linux/arm64 \
-t $IMAGE:$VERSION -t $IMAGE:$VERSION-$SHA \
--push .
This produces one multi-platform image under two immutable tags: a release version and a version-plus-commit tag that ties the image to its exact source code.
Troubleshooting Registry Errors
Frequently Asked Questions
What is the difference between a registry and a repository?
A registry is the service that hosts images, such as Docker Hub or Amazon ECR. A repository is a named collection of related images inside a registry, usually the versions of one application, such as bitcodematrix/api.
Is Docker Hub free?
Yes, for personal use with limits. Free accounts can publish public repositories and pull up to 200 images every 6 hours when logged in; anonymous users get 100 per IP address. Paid plans remove pull limits (subject to fair use) and add features for teams.
Can I use Amazon ECR outside AWS?
Yes. Any Docker host or Kubernetes cluster with network access and valid AWS credentials can pull from ECR. You pay for data transferred out of AWS, so ECR is most cost-effective for workloads running on AWS.
How long does an ECR login last?
The authorisation token from aws ecr get-login-password is valid for 12 hours. The Amazon ECR Docker Credential Helper refreshes it automatically.
Do I need Harbor, or is the basic registry enough?
The basic Distribution registry is enough for a single team that only needs storage with simple authentication. Choose Harbor when you need a web interface, multiple projects, role-based access, single sign-on, scanning, replication or retention policies.
How do I move images from Docker Hub to ECR?
Pull, re-tag and push each image, or copy it directly between registries with docker buildx imagetools create --tag <ecr-image> <dockerhub-image>, which also preserves multi-platform images. For ongoing use of public images, an ECR pull-through cache rule is simpler.
Conclusion and Next Steps
A registry is where your images live between build and run, and it deserves the same care as the rest of your production platform. Docker Hub is the default source of public and trusted base images; a self-hosted Distribution registry or Harbor gives you full control on your own infrastructure; and Amazon ECR provides a managed, IAM-secured registry with scanning, lifecycle policies and pull-through caching for workloads on AWS. Whichever you choose, use immutable version tags, scan and sign images, grant least-privilege access and clean up automatically.
Continue the Docker course with these tutorials on BitCodeMatrix:
Docker Architecture Explained — where registries fit alongside the client and daemon
Docker Commands Cheat Sheet — docker login, tag, push and pull at a glance
Build Smaller, Faster and More Secure Docker Images — shrink what you push
Image Tagging Strategy (next in the course) — semantic versions, commit SHAs and digests