Container Registry: Image Storage, Scanning, and Distribution
Set up and secure container registries for storing, scanning, and distributing container images across your CI/CD pipeline and clusters.
Container registries are the artifact stores for your CI/CD pipeline. This guide covers registry options, image tagging strategies, vulnerability scanning, and access control.
When to Use / When Not to Use
When registries make sense
A container registry is essential when you are building and deploying containerized applications. If your CI/CD pipeline produces Docker images, you need somewhere to store them between the build step and the deployment step. Even for small projects, a registry gives you a versioned history of your images that you cannot get from local Docker storage.
Use a registry when you need image promotion between environments. An image built once in the CI pipeline should flow from dev to staging to production without rebuilding. The registry holds the canonical artifact at each stage.
For teams with multiple services, a shared registry lets different pipelines pull base images and intermediate layers without repeatedly downloading from public sources. This speeds up builds and reduces external dependencies.
When to skip or simplify
If your application is not containerized and never will be, a container registry does not add value. Some projects are better served by plain artifact storage (S3, GCS) for JARs, binaries, or Helm charts.
For personal projects or experiments that never leave your local machine, running a local registry or skipping one entirely makes sense. You can always add a registry later when the project grows.
Registry Architecture Flow
flowchart LR
A[Developer] -->|docker build| B[CI Pipeline]
B -->|docker push| C[Container Registry]
C -->|docker pull| D[Staging Cluster]
C -->|docker pull| E[Production Cluster]
F[Scanner] -->|periodic scan| C
Registry Options
Cloud-provider registries:
| Provider | Service | Notes |
|---|---|---|
| AWS | ECR | Integrated with IAM, VPC endpoints |
| Azure | ACR | Geo-replication, webhook integration |
| GCP | GCR | Integrated with Cloud Build, IAM |
| Artifact Registry | Successor to GCR, multi-format |
Self-hosted options:
| Registry | Best For |
|---|---|
| Harbor | Enterprise with authentication, replication |
| GitLab Container Registry | GitLab-integrated deployments |
| Docker Hub | Public images, simple needs |
| ChartMuseum | Helm charts alongside images |
AWS ECR example:
# Create ECR repository
aws ecr create-repository \
--repository-name myapp/backend \
--image-scanning-configuration scanOnPush=true \
--encryption-configuration encryptionType=AES256
# Login to ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
# Push image
docker build -t myapp/backend:1.0.0 .
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp/backend:1.0.0
Azure ACR example:
# Create ACR
az acr create \
--resource-group mygroup \
--name myregistry \
--sku Standard \
--location eastus
# Enable admin user
az acr update -n myregistry --admin-enabled true
# Login and push
az acr login --name myregistry
docker build -t myregistry.azurecr.io/myapp:1.0.0 .
docker push myregistry.azurecr.io/myapp:1.0.0
Image Tagging Strategies
Tags identify image versions and control which code deploys where.
Common tagging patterns:
| Pattern | Example | Use Case |
|---|---|---|
| Git SHA | a1b2c3d | Precise traceability |
| Semantic version | 1.2.3 | Releases |
| Date-time | 202603251430 | CI/CD timestamps |
latest | latest | Default, avoid for prod |
| Environment | prod, staging | Environment promotion |
Recommended strategy for production:
# Always tag with multiple identifiers
IMAGE="myregistry.azurecr.io/myapp/backend"
# SHA for exact traceability
docker build -t $IMAGE:sha-$(git rev-parse --short HEAD)
# Semantic version from tag
docker build -t $IMAGE:$(cat VERSION)
# Date-time for CI builds
docker build -t $IMAGE:$(date +%Y%m%d%H%M%S)
# Don't use :latest in production manifests
# Instead:
kubectl set image deployment/myapp backend=$IMAGE:sha-a1b2c3d
Immutable tags for releases:
# Helm values for production deployment
image:
repository: myregistry.azurecr.io/myapp/backend
tag: "v1.2.3" # Immutable once released
pullPolicy: IfNotPresent # Only pulls if not present
Vulnerability Scanning
Integrate scanning into your pipeline to catch vulnerabilities before deployment.
Trivy in CI/CD:
# GitHub Actions
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: myregistry.azurecr.io/myapp:${{ github.sha }}
format: "sarif"
output: "trivy-results.sarif"
severity: "CRITICAL,HIGH"
exit-code: "1" # Fail on critical vulnerabilities
- name: Upload Trivy results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: "trivy-results.sarif"
Harbor scanning:
Harbor integrates Trivy and other scanners natively. Configure in /etc/harbor/harbor.yml:
# Enable CVE prevention
vulnerability:
severity: # Reject push if severity >= this level
- high
- critical
stop_uploading: true
stop_downloading: true
update_bulk: "1h"
Gatekeeper policy:
# OPA Gatekeeper constraint for image signatures
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredImageTag
metadata:
name: require-tag
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
namespaces: ["production"]
exemptImages:
- "myregistry.azurecr.io/base-image:*"
Image Promotion Between Environments
Promote images through environments with validation at each stage.
GitLab CI image promotion:
stages:
- build
- scan
- staging
- production
build:
stage: build
script:
- docker build -t $IMAGE:build-$CI_COMMIT_SHA .
- docker push $IMAGE:build-$CI_COMMIT_SHA
scan:
stage: scan
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE:build-$CI_COMMIT_SHA
allow_failure: false
promote:staging:
stage: staging
script:
- docker tag $IMAGE:build-$CI_COMMIT_SHA $IMAGE:staging-$CI_COMMIT_SHA
- docker push $IMAGE:staging-$CI_COMMIT_SHA
- curl -X POST "https://argo.example.com/api/v1/apps/myapp/sync" \
-d '{"prune":true,"dryRun":false}'
only:
- main
promote:production:
stage: production
script:
- docker tag $IMAGE:build-$CI_COMMIT_SHA $IMAGE:production-$CI_COMMIT_SHA
- docker push $IMAGE:production-$CI_COMMIT_SHA
- # Update deployment manifests or trigger ArgoCD
when: manual
only:
- main
Digest-based promotion for immutability:
# Get image digest
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' myregistry.azurecr.io/myapp:1.0.0)
echo $DIGEST
# Output: myregistry.azurecr.io/myapp@sha256:abc123...
# Deploy by digest (immutable)
docker run myregistry.azurecr.io/myapp@sha256:abc123...
Registry Caching for Faster Builds
Cache images to reduce build times and external dependencies.
GitHub Actions cache for Docker:
- uses: docker/setup-buildx-action@v3
with:
driver-opts: |
image=moby/buildkit:buildx-stable
network=host
- uses: docker/build-push-action@v5
with:
push: true
tags: myregistry.azurecr.io/myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Registry mirror for air-gapped environments:
# Deploy a pull-through cache
helm install registry-cache stable/docker-registry \
--set pullThrough.enabled=true \
--set cache.remoteURL=https://registry-1.docker.io
# Configure Docker daemon on nodes
# /etc/docker/daemon.json
{
"registry-mirrors": ["https://cache.mycorp.example.com"]
}
Access Control and Authentication
Control who can push, pull, and manage images.
AWS ECR IAM policies:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789:role/deploy-role"
},
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
]
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789:role/ci-service-account"
},
"Action": ["ecr:*"]
}
]
}
Azure RBAC for ACR:
# Reader role for production pods
az role assignment create \
--assignee-object-id $(kubectl get serviceaccount default -n production -o jsonpath='{.metadata.uid}') \
--role AcrPull \
--scope /subscriptions/.../resourceGroups/mygroup/providers/Microsoft.ContainerRegistry/registries/myregistry
# Contributor for CI/CD pipelines
az role assignment create \
--assignee $CI_SERVICE_PRINCIPAL_ID \
--role Contributor \
--scope /subscriptions/.../resourceGroups/mygroup/providers/Microsoft.ContainerRegistry/registries/myregistry
Kubernetes image pull secrets:
# Create image pull secret
kubectl create secret docker-registry acr-secret \
--docker-server=myregistry.azurecr.io \
--docker-username=myuser \
--docker-password=$(az keyvault secret show --name acr-password --vault-name myvault -o tsv --query value) \
--docker-email=myuser@example.com
# Reference in service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: production
imagePullSecrets:
- name: acr-secret
Production Failure Scenarios
Common Registry Failures
| Failure | Impact | Mitigation |
|---|---|---|
| Registry authentication token expired | Cannot pull or push images, deployments fail | Use service accounts with rotating credentials |
| Image not found after tag deletion | Deployment tries to pull deleted image | Never delete tags in production, use immutable tags |
| Quota exceeded on cloud registry | Cannot push new images | Monitor storage usage, set lifecycle policies |
| Network partition to registry | Builds cannot push, deployments cannot pull | Use multi-region replication, local registry cache |
| Vulnerability scan blocking deploys | Critical CVE stops production deployment | Define triage process for CVEs, do not scan blindly |
Image Pull Failures
flowchart TD
A[Pod Schedule] --> B{Pull Image}
B -->|Success| C[Pod Starts]
B -->|Fail| D{Error Type}
D -->|Image Not Found| E[Check tag exists in registry]
D -->|Auth Failed| F[Refresh pull secret]
D -->|Quota Exceeded| G[Delete old images]
E --> H[Redeploy]
F --> H
G --> H
Observability Hooks
What to monitor:
- Image push success/failure rate
- Pull request latency by region
- Storage consumption growth rate
- Vulnerability scan results trend
- Authentication failure spikes
# AWS ECR - check repository size
aws ecr describe-repositories --query 'repositories[].{name:repositoryName,size:imageScanningConfiguration.scanOnPush}'
# Azure ACR - check usage
az acr show-usage --name myregistry
# Docker Hub - check rate limits
curl -s -o /dev/null -w "%{http_code}" https://hub.docker.com/v2/
Common Pitfalls / Anti-Patterns
Using the :latest tag in production manifests
When your manifest references myapp:latest, you have no idea which image actually deployed. If you need to roll back, you cannot because :latest keeps changing. Always use specific immutable tags (commit SHA, semantic version, or date-based).
Not scanning images before deployment
Pushing unscanned images to production means you discover vulnerabilities after they are running. Integrate Trivy or similar scanners into your CI pipeline and block pushes when critical CVEs are found.
Leaving unused images in the registry
Old images accumulate and consume storage. Without lifecycle policies, your registry bill grows while nobody knows which images are actually in use. Set up retention policies that delete images not referenced by any deployment.
Mixing image sources in one pipeline
Using Docker Hub for base images, your private registry for app images, and a third-party registry for dependencies makes it hard to track where each piece comes from. Keep a clear lineage from build to registry to deployment.
Forgetting about cross-region replication
If your production cluster is in us-west-2 but your registry is only in us-east-1, every image pull crosses the country. Configure replication for the regions where your clusters run.
Quick Recap
Key Takeaways
- Use specific immutable tags (SHA, semver) instead of
:latestin production - Integrate vulnerability scanning into CI to catch CVEs before deployment
- Configure access control through cloud IAM, not shared passwords
- Set up registry replication for multi-region deployments
- Monitor push/pull success rates, storage growth, and scan results
Registry Health Checklist
# Verify you can pull your production image
docker pull myregistry.azurecr.io/myapp:prod && echo "Pull successful"
# Check for critical CVEs before deploying
trivy image --severity CRITICAL,HIGH myregistry.azurecr.io/myapp:v1.2.3
# List unused images (older than 90 days)
aws ecr list-images --repository-name myapp --filter '{"tagStatus": "UNTAGGED"}'
# Verify pull secret works in Kubernetes
kubectl get secret acr-secret --output jsonpath='{.data.\.dockerconfigjson}' | base64 -d
Trade-off Summary
| Registry | Hosted vs Self-Hosted | Auth | Cost | Best For |
|---|---|---|---|---|
| Docker Hub | Both | Docker Auth | Free tier / paid | Open source images |
| ECR | Cloud-hosted | IAM | Pay per storage + egress | AWS workloads |
| GCR / Artifact Registry | Cloud-hosted | IAM + VMDF | Pay per storage + egress | GCP workloads |
| ACR | Cloud-hosted | IAM + admin keys | Pay per storage + egress | Azure workloads |
| Harbor | Self-hosted | OIDC, LDAP, local | Infrastructure cost | Enterprise / air-gapped |
| GitHub Packages | Cloud-hosted | GitHub token | Storage + bandwidth | GitHub-native workflows |
| GitLab Container Registry | Cloud-hosted | GitLab token | Storage + bandwidth | GitLab-native workflows |
Conclusion
Container registries are critical infrastructure for secure, fast deployments. Use semantic or commit-based tagging for traceability, integrate vulnerability scanning into your pipeline, and enforce access control through cloud IAM or Kubernetes secrets. For more on CI/CD patterns, see our CI/CD Pipelines overview, and for deployment strategies, see our Deployment Strategies guide.
Category
Related Posts
Container Images: Building, Optimizing, and Distributing
Learn how Docker container images work, layer caching strategies, image optimization techniques, and how to publish your own images to container registries.
Docker Networking: From Bridge to Overlay
Master Docker's networking models—bridge, host, overlay, and macvlan—for connecting containers across hosts and distributed applications.
Docker Fundamentals
Learn Docker containerization fundamentals: images, containers, volumes, networking, and best practices for building and deploying applications.