Container Registries and Distribution¶
Overview¶
Building a container image on your laptop is only half the delivery story. Production systems pull images from a container registry — a remote store that versions, authenticates, and distributes OCI-compliant artifacts to hosts, CI runners, and orchestrators. Without a registry, every server would need a local build environment, reproducibility would collapse, and rollbacks would be impossible at scale.
This tutorial covers how registries work, how to authenticate and push to Docker Hub, Amazon Elastic Container Registry (ECR), and Google Artifact Registry (the modern replacement for Container Registry), and how to design tagging strategies that tie images to Git commits, environments, and release channels. You will practice tagging, pushing, pulling, inspecting manifests, and applying immutability patterns used in real pipelines.
This is Tutorial 11 in Module 4: Networking & Registry of the REBASH Academy Docker series. Complete Docker Networking Fundamentals first, then continue to Environment Variables and Secrets.
Prerequisites¶
- Docker Engine installed and running — see Docker Installation and Setup
- Ability to build a simple image — see Building Images with Dockerfile
- A Docker Hub account (free tier is sufficient for labs)
- Optional: AWS CLI and GCP
gcloudCLI for cloud registry sections - Basic understanding of Git tagging concepts
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Explain the role of container registries in build-once, deploy-many workflows
- Authenticate to Docker Hub, Amazon ECR, and Google Artifact Registry
- Tag images with semantic, Git SHA, and environment-specific conventions
- Push, pull, and inspect image manifests and digests
- Apply immutability and retention policies for production image promotion
- Describe how CI/CD pipelines integrate with private registries
Architecture Diagram¶
Registries sit between image builders and runtime consumers. The same image digest can be pulled by development laptops, CI agents, ECS tasks, GKE nodes, and Kubernetes clusters worldwide.
flowchart LR
DEV["Developer / CI Builder"]
BUILD[docker build]
REG["(Container Registry<br/>Hub / ECR / Artifact Registry")]
RUN1[Docker Host]
RUN2[Kubernetes Node]
RUN3["Cloud Run / ECS"]
DEV --> BUILD
BUILD -->|docker push| REG
REG -->|docker pull| RUN1
REG -->|kubelet pull| RUN2
REG -->|service pull| RUN3```
## Theory
### What Is a Container Registry?
A **container registry** stores **images** — layered filesystem snapshots with metadata (architecture, OS, labels, config). Registries implement the **OCI Distribution Specification**, so Docker, containerd, Podman, and Kubernetes use the same push/pull protocol over HTTPS.
Key concepts:
| Term | Meaning |
|------|---------|
| **Repository** | Named collection of related images (e.g., `myorg/api`) |
| **Tag** | Human-readable label pointing to a manifest (e.g., `v1.4.2`, `main-abc1234`) |
| **Digest** | Immutable SHA256 hash of the manifest (e.g., `sha256:abc123...`) |
| **Manifest** | JSON describing layers, config blob, and platform |
| **Namespace** | Organization or project scope (`library/nginx`, `123456789012.dkr.ecr...`) |
**Tags are mutable** — pushing `myapp:latest` again overwrites what `latest` points to. **Digests are immutable** — production deployments should pin digests or tags that are never reused.
### Docker Hub
**Docker Hub** is the default public registry. When you run `docker pull nginx`, Docker resolves `docker.io/library/nginx`. Hub provides:
- Public repositories (unlimited pulls with rate limits for anonymous users)
- Private repositories (limited on free accounts; paid plans for teams)
- Automated builds (legacy; most teams use CI instead)
- Organization namespaces for team ownership
Authentication uses `docker login docker.io` with a username and personal access token (PAT). Passwords are deprecated for CLI login; use Hub access tokens with appropriate scopes.
### Amazon Elastic Container Registry (ECR)
**ECR** is AWS's managed OCI registry. Each repository lives in a region under your AWS account ID. Image URIs look like:
```text
123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:v1.0.0 ECR features relevant to production:
- IAM integration — fine-grained push/pull policies via IAM roles and policies
- Image scanning — vulnerability scanning on push (Basic scanning or Inspector integration)
- Lifecycle policies — automatically expire untagged or old images to control storage cost
- Replication — cross-region and cross-account replication for DR and multi-region deploys
- Private by default — no public exposure unless you enable ECR Public
Authentication uses short-lived tokens from aws ecr get-login-password, piped to docker login. CI/CD on AWS typically uses IAM roles (OIDC or instance profiles) instead of long-lived keys.
Google Artifact Registry¶
Google's Artifact Registry (AR) replaced Container Registry (GCR) for new projects. Images are stored in regional repositories with URIs like:
Artifact Registry supports Docker images, Helm charts, Maven artifacts, and more in unified repositories. Authentication uses:
gcloud auth configure-docker REGION-docker.pkg.devfor developer workstations- Workload Identity Federation or service account keys in CI (prefer federation)
- IAM roles:
roles/artifactregistry.reader,roles/artifactregistry.writer
GKE pulls images from Artifact Registry when nodes have the appropriate service account permissions.
Tagging Strategies for Production¶
Poor tagging causes "works on my machine" deploys and impossible rollbacks. Mature teams combine several tag types:
| Tag pattern | Example | Use case |
|---|---|---|
| Semantic version | v2.1.0 | Release promotion, changelog alignment |
| Git commit SHA | sha-a1b2c3d4 or main-a1b2c3d4 | Immutable traceability to source |
| Branch name | feature-login-fix | Ephemeral CI builds (auto-prune) |
| Environment | staging, prod | Mutable pointers — use with caution |
| Build number | build-1842 | CI pipeline sequence |
| latest | latest | Dev convenience only — never sole prod tag |
Recommended production pattern:
- CI builds image tagged with Git SHA (immutable)
- After tests pass, additionally tag
v1.4.2and push - Deployment manifest pins digest or SHA tag
latestupdated only for developer convenience on default branch
Image Promotion and Immutability¶
Promotion moves a tested artifact through environments without rebuilding:
Because the digest is unchanged, staging and production run identical bits. Rebuilding per environment introduces drift.
Enable tag immutability where supported (ECR, Artifact Registry, Harbor) to prevent accidental overwrites of release tags.
Registry Security Essentials¶
- Use private registries for proprietary code; scan public base images
- Prefer short-lived credentials (ECR tokens expire in 12 hours)
- Grant least privilege — CI push role should not have delete on production repos
- Enable vulnerability scanning and block deploy on critical CVEs (policy in CI)
- Audit pull/push logs via CloudTrail (ECR) or Cloud Logging (GCP)
Hands-on Lab¶
These labs use a minimal nginx-based image. Adjust usernames and project IDs to match your accounts.
Step 1 – Build a lab image locally¶
Command:
mkdir -p /tmp/registry-lab && cd /tmp/registry-lab
printf 'FROM nginx:1.27-alpine\nLABEL maintainer="lab@example.com"\n' > Dockerfile
docker build -t registry-lab-web:local .
docker images registry-lab-web:local
Explanation: A tiny image keeps push/pull labs fast. The local tag stays on your machine until you retag for a registry.
Expected output:
Successfully tagged registry-lab-web:local
REPOSITORY TAG IMAGE ID CREATED SIZE
registry-lab-web local ... ... ...
Step 2 – Tag for Docker Hub¶
Replace YOUR_DOCKERHUB_USER with your Hub username.
Command:
export DOCKERHUB_USER="YOUR_DOCKERHUB_USER"
docker tag registry-lab-web:local "${DOCKERHUB_USER}/registry-lab-web:lab-v1"
docker tag registry-lab-web:local "${DOCKERHUB_USER}/registry-lab-web:sha-demo001"
docker images | grep registry-lab-web
Explanation: One image ID can have many tags. Tags are aliases; they do not duplicate layer storage locally.
Step 3 – Authenticate and push to Docker Hub¶
Create a Hub access token under Account Settings → Security → New Access Token.
Command:
docker login docker.io
docker push "${DOCKERHUB_USER}/registry-lab-web:lab-v1"
docker push "${DOCKERHUB_USER}/registry-lab-web:sha-demo001"
Explanation: docker login stores credentials in ~/.docker/config.json (base64-encoded, not encrypted — protect this file). Push uploads missing layers; subsequent pushes of the same layers are fast.
Expected output:
Step 4 – Inspect manifest and digest¶
Command:
docker inspect "${DOCKERHUB_USER}/registry-lab-web:lab-v1" | grep -m1 '"Id"'
docker manifest inspect "docker.io/${DOCKERHUB_USER}/registry-lab-web:lab-v1" 2>/dev/null || \
docker buildx imagetools inspect "docker.io/${DOCKERHUB_USER}/registry-lab-web:lab-v1"
Explanation: Production deploys often reference digest instead of tag. buildx imagetools queries the remote registry without a local pull.
Step 5 – Pull on a clean host (simulate deployment)¶
Command:
docker rmi "${DOCKERHUB_USER}/registry-lab-web:lab-v1" 2>/dev/null || true
docker pull "${DOCKERHUB_USER}/registry-lab-web:lab-v1"
docker run -d --name reg-lab-test -p 8080:80 "${DOCKERHUB_USER}/registry-lab-web:lab-v1"
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/
docker stop reg-lab-test && docker rm reg-lab-test
Explanation: Simulates what a production host does: pull by tag, run container, verify HTTP response.
Expected output:
Step 6 – Amazon ECR push workflow (AWS)¶
Requires AWS CLI configured with permissions for ECR. Replace region and repository name.
Command:
export AWS_REGION="us-east-1"
export ECR_REPO="registry-lab-web"
aws ecr describe-repositories --repository-names "${ECR_REPO}" 2>/dev/null || \
aws ecr create-repository --repository-name "${ECR_REPO}" --image-scanning-configuration scanOnPush=true
export ECR_URI=$(aws ecr describe-repositories --repository-names "${ECR_REPO}" \
--query 'repositories[0].repositoryUri' --output text)
aws ecr get-login-password --region "${AWS_REGION}" | \
docker login --username AWS --password-stdin "${ECR_URI%%/*}"
docker tag registry-lab-web:local "${ECR_URI}:v1-lab"
docker push "${ECR_URI}:v1-lab"
Explanation: ECR login tokens expire after 12 hours. CI pipelines refresh tokens on each job. scanOnPush enables vulnerability scanning.
Step 7 – Google Artifact Registry push workflow (GCP)¶
Replace project, region, and repository names. Enable Artifact Registry API first.
Command:
export GCP_PROJECT="my-gcp-project"
export GCP_REGION="us-central1"
export AR_REPO="docker-lab"
gcloud artifacts repositories describe "${AR_REPO}" \
--location="${GCP_REGION}" --project="${GCP_PROJECT}" 2>/dev/null || \
gcloud artifacts repositories create "${AR_REPO}" \
--repository-format=docker --location="${GCP_REGION}" --project="${GCP_PROJECT}"
gcloud auth configure-docker "${GCP_REGION}-docker.pkg.dev" --quiet
export AR_URI="${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT}/${AR_REPO}/registry-lab-web"
docker tag registry-lab-web:local "${AR_URI}:v1-lab"
docker push "${AR_URI}:v1-lab"
Explanation: Artifact Registry is regional. GKE clusters in the same region pull with lower latency and no cross-region egress charges.
Step 8 – Clean up lab resources¶
Command:
Explanation: Remove local tags after labs. Delete remote test repositories in Hub/ECR/AR consoles if no longer needed.
Commands & Code¶
| Command | Description | Example |
|---|---|---|
docker tag | Add registry-compatible tag to local image | docker tag myapp:local reg.io/org/myapp:v1 |
docker login | Authenticate to registry | docker login docker.io |
docker push | Upload image layers and manifest | docker push reg.io/org/myapp:v1 |
docker pull | Download image from registry | docker pull reg.io/org/myapp:v1 |
docker logout | Remove stored credentials | docker logout docker.io |
aws ecr get-login-password | ECR auth token | Pipe to docker login |
gcloud auth configure-docker | Configure Docker for Artifact Registry | Region-specific hostname |
docker buildx imagetools inspect | Remote manifest inspection | Shows digest and platforms |
CI tagging script pattern¶
Save as scripts/tag-and-push.sh in your pipeline repo. Uses environment variables set by CI — no secrets in the script itself.
#!/usr/bin/env bash
set -euo pipefail
IMAGE_NAME="${CI_REGISTRY_IMAGE:-myregistry.example.com/myapp}"
GIT_SHA="${CI_COMMIT_SHORT_SHA:-dev}"
VERSION="${CI_COMMIT_TAG:-}"
docker build -t "${IMAGE_NAME}:sha-${GIT_SHA}" .
docker push "${IMAGE_NAME}:sha-${GIT_SHA}"
if [ -n "${VERSION}" ]; then
docker tag "${IMAGE_NAME}:sha-${GIT_SHA}" "${IMAGE_NAME}:${VERSION}"
docker push "${IMAGE_NAME}:${VERSION}"
fi
Make executable: chmod +x scripts/tag-and-push.sh
Common Mistakes¶
Using only :latest in production
latest is a moving target. A redeploy without pinning may pull a different image than the one you tested. Always pin SHA tags or digests in production manifests.
Reusing release tags for different builds
Pushing v1.0.0 twice with different content breaks traceability. Enable tag immutability or use unique tags per build.
Embedding registry credentials in images or Dockerfiles
Never docker login inside a Dockerfile or bake tokens into layers. Use CI secrets, IAM roles, or credential helpers at runtime on the build host.
Ignoring registry rate limits
Docker Hub limits anonymous and free-tier pulls. Production clusters should authenticate or mirror images to a private registry.
Best Practices¶
Pin images by digest in production
Kubernetes, ECS task definitions, and Compose production files should reference image@sha256:... or immutable SHA tags tied to CI output.
Separate registries or repos per environment
Use distinct repositories or namespaces for dev, staging, and prod — or promote by retagging the same digest without rebuild.
Automate lifecycle cleanup
Untagged manifests and ancient feature-branch tags consume storage. Configure ECR lifecycle rules and Artifact Registry cleanup policies.
Mirror critical base images
Pull public bases (nginx, python, node) through your private registry to avoid Hub outages and rate limits affecting deploys.
Troubleshooting¶
| Issue | Cause | Solution |
|---|---|---|
denied: requested access to the resource is denied | Not logged in or wrong namespace | docker login; verify tag includes your username/org |
unauthorized: authentication required | Expired ECR/GCP token | Re-run aws ecr get-login-password or gcloud auth configure-docker |
name unknown: repository name not known to registry | Repo does not exist | Create repo in console/CLI before push |
| Push succeeds but pull fails on another host | Wrong architecture tag | Build multi-platform with docker buildx or match node arch |
toomanyrequests from Docker Hub | Rate limit exceeded | Authenticate; upgrade plan; use registry mirror |
| Digest mismatch after retag | Expected — tags point to same manifest | Use docker inspect to compare image IDs |
Summary¶
- Container registries distribute OCI images to every runtime environment using push/pull over HTTPS
- Docker Hub is the default public registry; ECR and Artifact Registry are managed private options on AWS and GCP
- Tags are human-readable and mutable; digests are immutable and preferred for production pinning
- Production tagging combines semantic versions, Git SHAs, and careful use of environment pointers
- CI/CD should build once, push with traceable tags, and promote the same digest across environments
Interview Questions¶
- What is the difference between an image tag and a digest?
- Why should production deployments avoid relying solely on
:latest? - How does authentication work for Amazon ECR?
- What replaced Google Container Registry, and how do image URIs differ?
- Describe an image promotion workflow from CI to production.
- What is an OCI-compliant registry, and why does it matter?
- How would you trace a running container back to the exact source code commit?
- What are ECR lifecycle policies used for?
- Explain the security risk of storing
docker logincredentials on shared build agents. - How do registry rate limits affect Kubernetes clusters pulling from Docker Hub?
Sample Answers (Questions 1, 5, and 7)
Q1 — Tag vs digest: A tag is a mutable string label (e.g., v1.4.2) that points to an image manifest. A digest is the SHA256 hash of that manifest (e.g., sha256:abc...) and cannot change without changing the image content. Two tags can reference the same digest; changing a tag's target does not change the digest of the underlying image.
Q5 — Promotion workflow: CI builds the image once and pushes with an immutable tag such as sha-a1b2c3d4. Automated tests run against that artifact. After approval, the same manifest receives additional tags (v1.4.2, staging) via retag-and-push or registry API — no rebuild. Production deployment references the digest or SHA tag, ensuring staging and prod run identical layers.
Q7 — Traceability: The image should be tagged with the Git commit SHA at build time (label org.opencontainers.image.revision or tag sha-a1b2c3d4). Query the running container's image with docker inspect or the orchestrator API, read the digest, and match it to CI build logs and the Git commit that triggered the pipeline.
Related Tutorials¶
- Docker – Category Overview
- Working with Docker Images
- Building Images with Dockerfile
- Docker Networking Fundamentals (previous in Module 4)
- Environment Variables and Secrets (next in Module 4)
- Docker in CI/CD Pipelines