Docker registries cheatsheet.
Image naming
[registry/]namespace/repo[:tag][@digest]
docker.io/library/nginx:latest
ghcr.io/myorg/myapp:v1.0
123456.dkr.ecr.us-east-1.amazonaws.com/myapp:v1
nginx@sha256:abc123...
Default registry is Docker Hub if omitted.
Tag + push
docker build -t myapp:v1 .
docker tag myapp:v1 ghcr.io/me/myapp:v1
docker push ghcr.io/me/myapp:v1
Multiple tags:
docker tag myapp ghcr.io/me/myapp:v1
docker tag myapp ghcr.io/me/myapp:latest
docker push --all-tags ghcr.io/me/myapp
Login
# Docker Hub
docker login -u USER
# GHCR
echo $GITHUB_TOKEN | docker login ghcr.io -u USER --password-stdin
# AWS ECR
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin 123456.dkr.ecr.us-east-1.amazonaws.com
# GCP Artifact Registry
gcloud auth configure-docker us-central1-docker.pkg.dev
Self-hosted registry
docker run -d \
-p 5000:5000 \
-v $(pwd)/registry:/var/lib/registry \
--restart=unless-stopped \
--name registry \
registry:2
docker tag myapp localhost:5000/myapp:v1
docker push localhost:5000/myapp:v1
For non-localhost: needs HTTPS or --insecure-registry in daemon.json.
Registry with auth + TLS
docker run -d \
-p 5000:5000 \
-v $(pwd)/certs:/certs \
-v $(pwd)/auth:/auth \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/cert.pem \
-e REGISTRY_HTTP_TLS_KEY=/certs/key.pem \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM="Registry" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:2
Generate htpasswd:
docker run --rm --entrypoint htpasswd httpd:2 -Bbn USER PASS > auth/htpasswd
Harbor (enterprise)
For scanning, signing, replication, RBAC, vulnerability management — use Harbor.
# Quick start
git clone https://github.com/goharbor/harbor
cd harbor
./install.sh
Mirror Docker Hub
// /etc/docker/daemon.json
{ "registry-mirrors": ["https://mirror.example.com"] }
Useful in low-egress environments.
Pull-through cache
docker run -d --restart=always \
-e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
-p 5000:5000 \
registry:2
Caches images from upstream.
docker save/load (no registry)
docker save -o myapp.tar myapp:v1
scp myapp.tar server:
ssh server "docker load < myapp.tar"
For air-gapped deployments.
Image promotion pipeline
dev → staging → prod
Same image, different tags:
docker pull ghcr.io/me/myapp:sha-abc123
docker tag ghcr.io/me/myapp:sha-abc123 ghcr.io/me/myapp:staging
docker push ghcr.io/me/myapp:staging
# After QA:
docker tag ghcr.io/me/myapp:sha-abc123 ghcr.io/me/myapp:prod
docker push ghcr.io/me/myapp:prod
Pin by SHA, promote by re-tag. No re-build.
Garbage collection
Registries hold unused blobs. Cleanup:
# Docker registry
docker exec registry registry garbage-collect /etc/docker/registry/config.yml
# Harbor: UI / API
Multi-registry CI
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
Cosign (signing)
cosign generate-key-pair
cosign sign --key cosign.key myreg/myapp:v1
cosign verify --key cosign.pub myreg/myapp:v1
Keyless with OIDC:
cosign sign myreg/myapp:v1
Uses GitHub OIDC. Public verification via certificate transparency log.
Pull rate limits
Docker Hub: 100 anonymous pulls / 6h / IP. 200 with login.
Solutions:
- Use mirror.
- Cache images in self-hosted registry.
- Pull-through cache.
- Switch to GHCR / similar without rate limits.
Common mistakes
- Pushing
latestonly — no version pinning. - Forgetting credentials in CI — push fails.
- Pulling from wrong region (slow).
- Storing huge unused images.
- Hardcoded registry URL — hard to migrate.
Read this next
If you want my registry + cosign workflow, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .