Docker registry vs Docker Hub: what is the difference?
A Docker registry is the type; Docker Hub is one instance of that type. Confusing the two is one of the more common framing mistakes when people start working with images outside of Docker Hub.
Theory
TL;DR
- Registry = a server that stores and distributes images per the OCI Distribution Spec. Generic term.
- Docker Hub = the specific public registry hosted by Docker Inc at
docker.io. The default when no registry is specified in the image name. - Same protocol, same image format, same client commands. The only differences between registries are: who runs them, what auth they require, what network they live on, what limits they impose.
- Production teams usually pick a private registry close to their workloads (AWS ECR for AWS-hosted services, GCR for GCP, GHCR for GitHub-centric teams).
- Self-hosted options (Harbor, the open-source
distribution/registry) exist when you need full control over storage, auth, and air-gapped operation.
Quick example
# Same image format, four different registries:
$ docker pull nginx:1.27 # docker.io implicit
$ docker pull ghcr.io/myorg/api:1.0 # GitHub
$ docker pull 123456789.dkr.ecr.eu-west-1.amazonaws.com/api:1.0 # AWS ECR
$ docker pull harbor.example.com/team/api:1.0 # self-hosted HarborThe registry name is the prefix before the first /. No prefix means Docker Hub.
What every registry does
Every OCI-compliant registry implements the same set of HTTP endpoints (the OCI Distribution Spec):
GET /v2/<name>/manifests/<tag-or-digest>- fetch an image manifestGET /v2/<name>/blobs/<digest>- fetch a layer or config blobPUT /v2/<name>/manifests/<tag>- push a manifestPOST /v2/<name>/blobs/uploads/thenPATCH/PUT- push a blobDELETE /v2/<name>/manifests/<digest>- delete (where allowed)
When you docker push myreg.example.com/myapp:1.0, the daemon hits these endpoints. There is no "Docker Hub protocol" - they all speak the same.
Docker Hub specifically
A registry, plus extras that come from Docker Inc running it:
- Free public repos, unlimited count.
- Free tier private repos, with strict limits beyond which you pay.
- Official Images program: curated images for
nginx,postgres,redis, etc. Maintained by Docker Inc and the upstream projects. - Verified Publisher program: vendor-published images (e.g.,
mongo,bitnami/...). - Anonymous pull rate limit: 100 pulls per 6 hours per IP. Authenticated pulls have higher limits.
- Webhooks on push, integrations with GitHub/Bitbucket for autobuilds.
- Search and tag browsing UI at
hub.docker.com.
If you remove the extras and look only at the protocol, Docker Hub is just a really big registry.
When to use which registry
Docker Hub - good for: open-source projects, learning, public images
AWS ECR - good for: AWS-hosted production (no egress fees from same region)
Google GCR/Artifact - good for: GCP-hosted production
Azure ACR - good for: Azure-hosted production
GitHub GHCR - good for: GitHub-centric teams, free for public
Harbor - good for: self-hosted, vulnerability scanning, air-gapped
GitLab Registry - good for: GitLab-centric CI/CD
Quay (Red Hat) - good for: enterprise OpenShift workflowsA common production pattern: build in CI -> push to two registries (a primary close to prod, a backup somewhere else) -> deploy from the primary.
Pull-through caches and mirrors
Docker Hub's anonymous rate limit caused real CI outages around 2020-2021. Modern fix: pull-through caches.
your CI / cluster -> caching mirror (ECR Pull-through, GAR mirror, Harbor) -> Docker Hub
First pull goes to Docker Hub once, gets cached in your mirror. Subsequent pulls hit the mirror only. You stop burning rate limits, and pulls are faster (closer network path).
Authentication models
| Registry | Typical auth |
|---|---|
| Docker Hub | Username + personal access token |
| AWS ECR | IAM role (aws ecr get-login-password | docker login --password-stdin) |
| Google Artifact Registry | Service account key or workload identity |
| GitHub GHCR | GitHub PAT or GITHUB_TOKEN in Actions |
| Harbor | Robot accounts (long-lived tokens scoped to a project) |
| Self-hosted (basic) | htpasswd, OIDC, or LDAP |
The docker login <registry> command works the same for all. The credential mechanism differs.
Common mistakes
Calling Docker Hub "the registry"
It is a registry, not the registry. Saying "push it to the registry" without naming which one leads to confused new hires. "Push to Docker Hub" or "push to our ECR" is more precise.
Forgetting the registry prefix on push
# WRONG: tries to push to Docker Hub under your username
$ docker tag myapp:1.0 myapp:1.0
$ docker push myapp:1.0
# requested access to the resource is denied
# RIGHT for ECR
$ docker tag myapp:1.0 1234.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0
$ docker push 1234.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0Without the registry prefix, Docker assumes Docker Hub. The error message is misleading.
Hitting Docker Hub anonymous rate limits and not realizing
ERROR: toomanyrequests: You have reached your pull rate limit.
You may increase the limit by authenticating and upgrading.Common in CI behind shared NAT IPs. Fix: authenticate (docker login) or set up a pull-through cache. Anonymous limit is 100 per 6h per IP - a busy CI shared with 20 jobs can burn it in minutes.
Self-hosting a registry without auth
The open-source distribution registry runs without authentication by default. Useful for testing, terrible for production. An exposed registry is read/write to anyone who can reach it. Front it with a reverse proxy that does basic auth, OIDC, or Harbor's full access control.
Mixing registry mirrors with credentials
If you point Docker at a private mirror via registry-mirrors, that mirror needs your credentials too. A common debugging tale: image pulls work locally but fail in CI because the mirror is configured but the CI has not authenticated to it.
Real-world usage
- Cloud-native production: ECR/GCR/ACR for the cloud you run on. Pulls are within the same VPC region, so they are fast and free of egress.
- Multi-cloud / on-prem: Harbor as a single registry across clouds, with replication policies that mirror images to cloud-specific registries near each region.
- Open-source projects: Docker Hub for the public image, GHCR mirror for free CI on public PRs, no Docker Hub rate-limit problems for contributors.
- Air-gapped environments (banks, defense, classified): internal Harbor or
distributioninstance behind a one-way sync from public registries. Production never reaches the public internet directly.
Follow-up questions
Q: If I push to AWS ECR, can someone with Docker pull from it without using AWS tools?
A: Only if they authenticate. The pull command itself is plain docker pull, but ECR requires an IAM-derived token. aws ecr get-login-password | docker login ... produces a short-lived token (12 hours) that the daemon uses. After that, docker pull works as usual.
Q: Can the same image live in multiple registries?
A: Yes. Tag the image once for each registry and push:
$ docker tag myapp:1.0 docker.io/youruser/myapp:1.0
$ docker tag myapp:1.0 ghcr.io/youruser/myapp:1.0
$ docker push docker.io/youruser/myapp:1.0
$ docker push ghcr.io/youruser/myapp:1.0Same bytes, two homes. Used for redundancy, migrations, and serving the same image to consumers in different ecosystems.
Q: What is the difference between Docker Hub and GitHub Container Registry?
A: Both serve OCI images. GHCR is integrated with GitHub: it uses your GitHub account, integrates with GITHUB_TOKEN in Actions, and ties image visibility to repo visibility. Docker Hub is independent. For a project hosted on GitHub, GHCR removes one set of credentials and gives you free public hosting without Docker Hub's rate limits on pulls.
Q: Can I run my own registry?
A: Yes. The open-source CNCF distribution project (formerly docker/distribution) is the reference implementation. docker run -d -p 5000:5000 registry:2 gives you a running registry in seconds. For production, use Harbor on top of distribution for auth, scanning, and a UI. Or run distribution directly behind nginx with basic auth.
Q: (Senior) How would you design image distribution for a global Kubernetes deployment with 50+ regions?
A: A regional mirror per cluster (or per cloud region), populated by a hub-and-spoke replication. Build once in CI, push to a primary registry (Harbor or your cloud's), let a replication policy mirror to each regional registry. Pull-through caches handle public images. Consumers in each region pull only from their nearest mirror, so a single registry outage does not stop deploys globally. The trade-off is operational complexity around image freshness and replication lag.
Examples
Authenticating against four different registries
# Docker Hub
$ docker login -u myuser # type personal access token
# AWS ECR (token auto-rotates every 12h)
$ aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
# GitHub Container Registry
$ echo $GITHUB_PAT | docker login ghcr.io -u myuser --password-stdin
# Self-hosted Harbor
$ docker login harbor.example.com -u robot$ci -p <robot-token>Four different auth flows, one CLI, four entries in ~/.docker/config.json after.
Replicating an image from Docker Hub to ECR
# Pull from Hub
$ docker pull nginx:1.27-alpine
# Tag for ECR
$ docker tag nginx:1.27-alpine \
123456789.dkr.ecr.us-east-1.amazonaws.com/mirror/nginx:1.27-alpine
# Push to ECR
$ docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/mirror/nginx:1.27-alpineNow your AWS workloads can pull nginx from ECR (in-region, free, fast) instead of Hub (cross-region, rate-limited). Production setups automate this with crane copy or a Harbor replication rule.
Running a private registry locally for a quick test
$ docker run -d -p 5000:5000 --name registry registry:2
$ docker tag myapp:1.0 localhost:5000/myapp:1.0
$ docker push localhost:5000/myapp:1.0
The push refers to repository [localhost:5000/myapp]
...
1.0: digest: sha256:9f8e7d6c... size: 1247
$ docker pull localhost:5000/myapp:1.0A fully working registry in one command. No auth, no TLS, only good for local testing - but it shows that Docker Hub is one specific server, and any OCI-spec-compliant alternative is interchangeable at the protocol level.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.
Comments
No comments yet