Container Registries
Essential guide for managing container images across registries with docker, skopeo, and registry-specific CLI tools.
Container Registries
Essential guide for managing container images across registries with docker, skopeo, and registry-specific CLI tools.
Overview
Container registries are centralised storage and distribution systems for container images. They enable versioning, access control, vulnerability scanning, and efficient distribution of container images across development and production environments. Registries support standard OCI (Open Container Initiative) specifications, ensuring compatibility across container runtimes.
graph TB
subgraph "Container Registry Ecosystem"
Developer[Developer] -->|docker push| Registry[Container Registry]
CI[CI/CD Pipeline] -->|push images| Registry
Registry -->|pull images| K8s[Kubernetes]
Registry -->|pull images| Docker[Docker Host]
subgraph "Registry Components"
Registry --> Storage[(Blob Storage)]
Registry --> Auth[Authentication]
Registry --> Scan[Vulnerability Scanner]
Registry --> API[Registry API v2]
end
end
Skopeo
Skopeo is a command-line utility for operations on container images and registries without requiring a Docker daemon. It can inspect, copy, delete, and sync images across registries.
flowchart LR
A[Source Registry] -->|skopeo copy| B[Destination Registry]
A -->|skopeo inspect| C[Image Metadata]
A -->|skopeo delete| D[Remove Image]
E[Local Directory] -->|skopeo copy| A
A -->|skopeo sync| F[Multiple Registries]
Key Advantages
- Daemonless: No Docker daemon required
- Efficient: Direct registry-to-registry transfers without local storage
- Comprehensive: Supports inspection, copying, deleting, and syncing
- Multi-format: Works with Docker, OCI, and other image formats
- Authentication: Supports multiple authentication methods
Installation
# Debian/Ubuntu
apt-get update && apt-get install -y skopeo
# RHEL/CentOS/Fedora
dnf install -y skopeo
# macOS (Homebrew)
brew install skopeo
# Verify installation
skopeo --version
Inspecting Images
# Inspect remote image without pulling
skopeo inspect docker://docker.io/nginx:latest
# Inspect with raw manifest
skopeo inspect --raw docker://nginx:latest
# Get specific information (using jq)
skopeo inspect docker://nginx:latest | jq '.Layers'
# Inspect image digest
skopeo inspect docker://nginx:latest | jq -r '.Digest'
# Check image configuration
skopeo inspect --config docker://nginx:latest
# List tags for a repository
skopeo list-tags docker://docker.io/nginx
Copying Images
# Copy between registries
skopeo copy \
docker://source-registry.io/myapp:1.0 \
docker://dest-registry.io/myapp:1.0
# Copy to local directory (OCI format)
skopeo copy \
docker://nginx:latest \
dir:/tmp/nginx-image
# Copy to local directory (Docker format)
skopeo copy \
docker://nginx:latest \
docker-archive:/tmp/nginx.tar
# Copy with authentication
skopeo copy \
--src-creds user:password \
--dest-creds user:password \
docker://source.io/myapp:1.0 \
docker://dest.io/myapp:1.0
# Copy all images with a tag pattern
skopeo copy --all \
docker://registry.io/myapp:latest \
docker://backup-registry.io/myapp:latest
# Copy preserving digests
skopeo copy --preserve-digests \
docker://source.io/myapp:1.0 \
docker://dest.io/myapp:1.0
Authentication with Skopeo
# Login to registry (creates entry in auth file)
skopeo login registry.example.com \
--username myuser \
--password mypassword
# Use authentication file
skopeo copy \
--authfile ~/.docker/config.json \
docker://source.io/myapp:1.0 \
docker://dest.io/myapp:1.0
# Provide credentials inline
skopeo inspect \
--creds username:password \
docker://private-registry.io/myapp:latest
# Use TLS certificate
skopeo copy \
--src-cert-dir /path/to/certs \
docker://source.io/myapp:1.0 \
docker://dest.io/myapp:1.0
# Skip TLS verification (insecure - not recommended)
skopeo inspect \
--tls-verify=false \
docker://insecure-registry.io/myapp:latest
Deleting Images
# Delete image by tag
skopeo delete docker://registry.io/myapp:old-version
# Delete with authentication
skopeo delete \
--creds user:password \
docker://registry.io/myapp:old-version
# Delete by digest
skopeo delete docker://registry.io/myapp@sha256:abc123...
Syncing Images
# Sync all tags from source to destination
skopeo sync \
--src docker --dest docker \
registry.io/myapp \
dest-registry.io/myapp
# Sync specific tags using YAML config
# sync-config.yaml:
# registry.io:
# images:
# myapp: ["latest", "1.0", "1.1"]
skopeo sync \
--src yaml --dest docker \
sync-config.yaml \
dest-registry.io
# Sync from directory to registry
skopeo sync \
--src dir --dest docker \
/local/images \
registry.io
# Dry-run sync operation
skopeo sync \
--dry-run \
--src docker --dest docker \
registry.io/myapp \
dest-registry.io/myapp
Advanced Skopeo Operations
# Get image layers
skopeo inspect docker://nginx:latest | jq '.Layers[]'
# Compare two images
diff <(skopeo inspect docker://nginx:1.24) \
<(skopeo inspect docker://nginx:1.25)
# Standalone signature verification
skopeo standalone-verify \
/path/to/signature \
/path/to/policy.json \
docker://registry.io/myapp:1.0
# Convert image formats
skopeo copy \
docker://nginx:latest \
oci:/tmp/nginx-oci:latest
# Copy multi-arch images
skopeo copy --all \
docker://nginx:latest \
docker://registry.io/nginx:latest
Troubleshooting Skopeo
| Issue | Solution |
|---|---|
| Authentication failures | Check credentials with skopeo login or use --creds flag |
| TLS certificate errors | Use --cert-dir or temporarily --tls-verify=false (insecure) |
| Permission denied | Verify user has push/pull permissions on registry |
| Image not found | Check image name, tag, and registry URL are correct |
| Digest mismatch | Ensure source image hasn't changed during copy |
| Slow transfers | Use --dest-compress for faster network transfers |
Docker Hub
Docker Hub is the default public registry for Docker images, offering both public and private repositories.
Authentication
# Login to Docker Hub
docker login
# Login with username and password
docker login -u username -p password
# Login using token
docker login -u username --password-stdin <<< "$DOCKER_TOKEN"
# Logout
docker logout
Pushing and Pulling
# Tag image for Docker Hub
docker tag myapp:1.0 username/myapp:1.0
# Push to Docker Hub
docker push username/myapp:1.0
# Pull from Docker Hub
docker pull username/myapp:1.0
# Pull official image
docker pull nginx:latest
# Pull by digest
docker pull username/myapp@sha256:abc123...
Rate Limits
Docker Hub enforces rate limits on image pulls:
- Unauthenticated: 100 pulls per 6 hours per IP
- Free account: 200 pulls per 6 hours
- Pro/Team: Unlimited pulls
# Check rate limit status
curl -s -I https://registry-1.docker.io/v2/ | grep -i ratelimit
# Use authentication to increase limits
docker login
docker pull nginx:latest
Best Practices
# Use specific tags, not 'latest'
docker pull nginx:1.25-alpine
# Use multi-stage builds to reduce image size
# In Dockerfile:
# FROM node:18 AS build
# ...
# FROM node:18-alpine
# COPY --from=build ...
# Tag with semantic versioning
docker tag myapp:latest username/myapp:1.2.3
docker tag myapp:latest username/myapp:1.2
docker tag myapp:latest username/myapp:1
GitHub Container Registry (GHCR)
GitHub Container Registry integrates with GitHub repositories and Actions, supporting OCI-compliant images.
sequenceDiagram
participant Dev as Developer
participant GH as GitHub Actions
participant GHCR as ghcr.io
participant K8s as Kubernetes
Dev->>GH: Push code
GH->>GH: Build image
GH->>GHCR: Push image
K8s->>GHCR: Pull image
Authentication
# Create Personal Access Token (PAT) with read:packages, write:packages scopes
# Export token
export CR_PAT=YOUR_TOKEN
# Login to GHCR
echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin
# Login with skopeo
echo $CR_PAT | skopeo login ghcr.io -u USERNAME --password-stdin
# Use in GitHub Actions (automatic)
# ${{ secrets.GITHUB_TOKEN }} is provided automatically
Image Management
# Tag for GHCR (lowercase required)
docker tag myapp:1.0 ghcr.io/username/myapp:1.0
# Push to GHCR
docker push ghcr.io/username/myapp:1.0
# Pull from GHCR
docker pull ghcr.io/username/myapp:1.0
# List package versions via API
curl -H "Authorization: Bearer $CR_PAT" \
https://api.github.com/users/USERNAME/packages/container/myapp/versions
# Delete package version via API
curl -X DELETE \
-H "Authorization: Bearer $CR_PAT" \
https://api.github.com/users/USERNAME/packages/container/myapp/versions/VERSION_ID
GitHub Actions Integration
name: Build and Push to GHCR
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
Package Visibility
# Make package public via GitHub UI
# Settings → Packages → Package Settings → Change Visibility
# Link package to repository
# Add label in Dockerfile:
LABEL org.opencontainers.image.source=https://github.com/username/repo
Amazon Elastic Container Registry (ECR)
AWS ECR is a fully managed Docker container registry integrated with AWS services.
graph LR
A[Local] -->|docker push| B[ECR Private]
A -->|docker push| C[ECR Public]
B -->|pull| D[ECS]
B -->|pull| E[EKS]
B -->|pull| F[EC2]
C -->|pull anonymous| G[Anyone]
Setup and Authentication
# Install AWS CLI
# Configure credentials
aws configure
# Get login password and login to ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
123456789012.dkr.ecr.us-east-1.amazonaws.com
# Create repository
aws ecr create-repository \
--repository-name myapp \
--region us-east-1
# List repositories
aws ecr describe-repositories --region us-east-1
# Create repository with image scanning
aws ecr create-repository \
--repository-name myapp \
--image-scanning-configuration scanOnPush=true \
--region us-east-1
Image Management
# Tag image for ECR
docker tag myapp:1.0 \
123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0
# Push to ECR
docker push \
123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0
# Pull from ECR
docker pull \
123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:1.0
# List images in repository
aws ecr list-images \
--repository-name myapp \
--region us-east-1
# Describe images
aws ecr describe-images \
--repository-name myapp \
--region us-east-1
# Delete image
aws ecr batch-delete-image \
--repository-name myapp \
--image-ids imageTag=old-version \
--region us-east-1
Lifecycle Policies
# Create lifecycle policy (delete untagged images after 7 days)
cat > lifecycle-policy.json <<EOF
{
"rules": [
{
"rulePriority": 1,
"description": "Remove untagged images after 7 days",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 7
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 2,
"description": "Keep only 10 most recent images",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": {
"type": "expire"
}
}
]
}
EOF
# Apply lifecycle policy
aws ecr put-lifecycle-policy \
--repository-name myapp \
--lifecycle-policy-text file://lifecycle-policy.json \
--region us-east-1
Cross-Region Replication
# Create replication configuration
cat > replication-config.json <<EOF
{
"rules": [
{
"destinations": [
{
"region": "eu-west-1",
"registryId": "123456789012"
}
]
}
]
}
EOF
# Apply replication configuration
aws ecr put-replication-configuration \
--replication-configuration file://replication-config.json
ECR Public
# Authenticate to ECR Public
aws ecr-public get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
public.ecr.aws
# Create public repository
aws ecr-public create-repository \
--repository-name myapp \
--region us-east-1
# Tag and push
docker tag myapp:1.0 public.ecr.aws/username/myapp:1.0
docker push public.ecr.aws/username/myapp:1.0
# Pull (no authentication needed)
docker pull public.ecr.aws/username/myapp:1.0
IAM Policies for ECR
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
],
"Resource": "arn:aws:ecr:us-east-1:123456789012:repository/myapp"
}
]
}
Google Container Registry (GCR) and Artifact Registry
GCP offers two registry services: GCR (legacy) and Artifact Registry (recommended).
graph TB
subgraph "GCP Container Registries"
AR[Artifact Registry<br/>Recommended]
GCR[Container Registry<br/>Legacy]
end
A[Local] -->|push| AR
A -->|push| GCR
AR -->|pull| B[GKE]
AR -->|pull| C[Cloud Run]
GCR -->|migrate| AR
Artifact Registry (Recommended)
# Enable Artifact Registry API
gcloud services enable artifactregistry.googleapis.com
# Create repository
gcloud artifacts repositories create myapp \
--repository-format=docker \
--location=us-central1 \
--description="My application images"
# List repositories
gcloud artifacts repositories list
# Configure Docker authentication
gcloud auth configure-docker us-central1-docker.pkg.dev
# Tag image
docker tag myapp:1.0 \
us-central1-docker.pkg.dev/project-id/myapp/myapp:1.0
# Push to Artifact Registry
docker push \
us-central1-docker.pkg.dev/project-id/myapp/myapp:1.0
# Pull from Artifact Registry
docker pull \
us-central1-docker.pkg.dev/project-id/myapp/myapp:1.0
# List images
gcloud artifacts docker images list \
us-central1-docker.pkg.dev/project-id/myapp
# Delete image
gcloud artifacts docker images delete \
us-central1-docker.pkg.dev/project-id/myapp/myapp:1.0
Container Registry (Legacy — shut down)
Container Registry was shut down on 18 March 2025. Writes are disabled and the service is fully replaced by Artifact Registry.
gcr.ioURLs now resolve to Artifact Registry-backed repositories (set up automatically, or via the transition guide). New work should target Artifact Registry directly; the commands below are retained only for reference to existinggcr.ioURLs.
# Configure Docker for GCR (gcr.io now served by Artifact Registry)
gcloud auth configure-docker
# Tag for GCR (uses gcr.io, us.gcr.io, eu.gcr.io, or asia.gcr.io)
docker tag myapp:1.0 gcr.io/project-id/myapp:1.0
# Push to GCR
docker push gcr.io/project-id/myapp:1.0
# Pull from GCR
docker pull gcr.io/project-id/myapp:1.0
# List images
gcloud container images list --repository=gcr.io/project-id
# List tags
gcloud container images list-tags gcr.io/project-id/myapp
# Delete image
gcloud container images delete gcr.io/project-id/myapp:1.0 --quiet
Vulnerability Scanning
# Enable vulnerability scanning (Artifact Registry)
gcloud services enable containerscanning.googleapis.com
# Scan on push is automatic in Artifact Registry
# View scan results
gcloud artifacts docker images list \
us-central1-docker.pkg.dev/project-id/myapp \
--show-occurrences
# Get detailed vulnerability report
gcloud artifacts docker images describe \
us-central1-docker.pkg.dev/project-id/myapp/myapp:1.0 \
--show-all-metadata
IAM Permissions
# Grant pull access
gcloud artifacts repositories add-iam-policy-binding myapp \
--location=us-central1 \
--member=serviceAccount:my-service@project-id.iam.gserviceaccount.com \
--role=roles/artifactregistry.reader
# Grant push access
gcloud artifacts repositories add-iam-policy-binding myapp \
--location=us-central1 \
--member=serviceAccount:ci-sa@project-id.iam.gserviceaccount.com \
--role=roles/artifactregistry.writer
Azure Container Registry (ACR)
Azure Container Registry provides private Docker registry hosting with integrated Azure AD authentication.
Setup and Authentication
# Create resource group
az group create --name myResourceGroup --location eastus
# Create ACR (Basic, Standard, or Premium)
az acr create \
--resource-group myResourceGroup \
--name myregistry \
--sku Standard
# Login to ACR
az acr login --name myregistry
# Get login server
az acr show --name myregistry --query loginServer --output table
# Login with docker directly
az acr credential show --name myregistry
docker login myregistry.azurecr.io -u username -p password
# Enable admin user (for testing only)
az acr update --name myregistry --admin-enabled true
Image Management
# Tag image for ACR
docker tag myapp:1.0 myregistry.azurecr.io/myapp:1.0
# Push to ACR
docker push myregistry.azurecr.io/myapp:1.0
# Pull from ACR
docker pull myregistry.azurecr.io/myapp:1.0
# List repositories
az acr repository list --name myregistry --output table
# Show tags
az acr repository show-tags \
--name myregistry \
--repository myapp \
--output table
# Delete image
az acr repository delete \
--name myregistry \
--image myapp:1.0
ACR Tasks (CI/CD)
# Build image in ACR (serverless)
az acr build \
--registry myregistry \
--image myapp:1.0 \
--file Dockerfile \
.
# Create automated build task
az acr task create \
--registry myregistry \
--name buildtask \
--image myapp:{{.Run.ID}} \
--context https://github.com/username/repo.git \
--file Dockerfile \
--git-access-token $GITHUB_PAT
# Run task manually
az acr task run --registry myregistry --name buildtask
# List task runs
az acr task list-runs --registry myregistry --output table
Geo-Replication (Premium SKU)
# Upgrade to Premium SKU
az acr update --name myregistry --sku Premium
# Create replication
az acr replication create \
--registry myregistry \
--location westus
# List replications
az acr replication list --registry myregistry --output table
Security Scanning
# Enable Microsoft Defender for containers
az security pricing create \
--name Containers \
--tier Standard
# View vulnerability assessment results
az acr repository show-tags \
--name myregistry \
--repository myapp \
--detail
Service Principal Authentication
# Create service principal
az ad sp create-for-rbac \
--name acr-service-principal \
--scopes /subscriptions/{subscription-id}/resourceGroups/myResourceGroup/providers/Microsoft.ContainerRegistry/registries/myregistry \
--role acrpull
# Use service principal credentials
docker login myregistry.azurecr.io \
-u $SP_APP_ID \
-p $SP_PASSWORD
Harbor
Harbor is an open-source registry that extends Docker Registry with enterprise features like RBAC, replication, and vulnerability scanning.
graph TB
subgraph Harbor
Portal[Harbor Portal]
Core[Harbor Core]
Registry[Registry Service]
JobService[Job Service]
Portal --> Core
Core --> Registry
Core --> JobService
subgraph Security
Scanner[Trivy Scanner]
Notary[Notary Server]
end
Core --> Scanner
Core --> Notary
DB[(PostgreSQL)]
Redis[(Redis)]
Storage[(Blob Storage)]
Core --> DB
Core --> Redis
Registry --> Storage
end
Installation
# Download Harbor installer
wget https://github.com/goharbor/harbor/releases/download/v2.9.0/harbor-offline-installer-v2.9.0.tgz
# Extract
tar xvf harbor-offline-installer-v2.9.0.tgz
cd harbor
# Configure
cp harbor.yml.tmpl harbor.yml
# Edit harbor.yml with your settings
# Install Harbor
sudo ./install.sh
# Install with Trivy scanner
sudo ./install.sh --with-trivy
# Install with Notary for image signing
sudo ./install.sh --with-notary
Authentication and Projects
# Login to Harbor
docker login harbor.example.com -u admin
# Login with skopeo
skopeo login harbor.example.com -u admin
# Create project via API
curl -X POST "https://harbor.example.com/api/v2.0/projects" \
-H "authorization: Basic YWRtaW46SGFyYm9yMTIzNDU=" \
-H "content-type: application/json" \
-d '{
"project_name": "myproject",
"public": false
}'
Image Management
# Tag image for Harbor
docker tag myapp:1.0 harbor.example.com/myproject/myapp:1.0
# Push to Harbor
docker push harbor.example.com/myproject/myapp:1.0
# Pull from Harbor
docker pull harbor.example.com/myproject/myapp:1.0
# Copy between Harbor projects with skopeo
skopeo copy \
docker://harbor.example.com/project1/myapp:1.0 \
docker://harbor.example.com/project2/myapp:1.0
Replication
# Create replication rule via API
curl -X POST "https://harbor.example.com/api/v2.0/replication/policies" \
-H "authorization: Basic YWRtaW46SGFyYm9yMTIzNDU=" \
-H "content-type: application/json" \
-d '{
"name": "replicate-to-backup",
"src_registry": {
"id": 1
},
"dest_registry": {
"id": 2
},
"trigger": {
"type": "manual"
},
"filters": [
{
"type": "name",
"value": "myproject/**"
}
]
}'
# Trigger replication manually
curl -X POST \
"https://harbor.example.com/api/v2.0/replication/executions" \
-H "authorization: Basic YWRtaW46SGFyYm9yMTIzNDU=" \
-H "content-type: application/json" \
-d '{"policy_id": 1}'
Vulnerability Scanning
# Scan image via API
curl -X POST \
"https://harbor.example.com/api/v2.0/projects/myproject/repositories/myapp/artifacts/1.0/scan" \
-H "authorization: Basic YWRtaW46SGFyYm9yMTIzNDU="
# Get scan results
curl -X GET \
"https://harbor.example.com/api/v2.0/projects/myproject/repositories/myapp/artifacts/1.0" \
-H "authorization: Basic YWRtaW46SGFyYm9yMTIzNDU=" | jq '.scan_overview'
Robot Accounts
# Create robot account for CI/CD
curl -X POST "https://harbor.example.com/api/v2.0/robots" \
-H "authorization: Basic YWRtaW46SGFyYm9yMTIzNDU=" \
-H "content-type: application/json" \
-d '{
"name": "ci-robot",
"duration": -1,
"level": "project",
"permissions": [
{
"kind": "project",
"namespace": "myproject",
"access": [
{"resource": "repository", "action": "push"},
{"resource": "repository", "action": "pull"}
]
}
]
}'
Garbage Collection
# Run garbage collection via API
curl -X POST \
"https://harbor.example.com/api/v2.0/system/gc/schedule" \
-H "authorization: Basic YWRtaW46SGFyYm9yMTIzNDU=" \
-H "content-type: application/json" \
-d '{
"schedule": {
"type": "Weekly",
"cron": "0 0 * * 0"
},
"parameters": {
"delete_untagged": true
}
}'
Quay.io
Quay.io is a container registry by Red Hat offering security scanning, geo-replication, and advanced access controls.
Authentication
# Login to Quay.io
docker login quay.io
# Login with robot account
docker login quay.io -u 'username+robotname' -p 'robottoken'
# Login with encrypted password
echo $QUAY_PASSWORD | docker login quay.io -u username --password-stdin
# Login with skopeo
skopeo login quay.io -u username
Image Management
# Tag for Quay.io
docker tag myapp:1.0 quay.io/username/myapp:1.0
# Push to Quay.io
docker push quay.io/username/myapp:1.0
# Pull from Quay.io
docker pull quay.io/username/myapp:1.0
# Make repository public via API
curl -X POST \
-H "Authorization: Bearer $QUAY_TOKEN" \
-H "Content-Type: application/json" \
https://quay.io/api/v1/repository/username/myapp/changevisibility \
-d '{"visibility": "public"}'
Robot Accounts
# Create robot account via API
curl -X PUT \
-H "Authorization: Bearer $QUAY_TOKEN" \
-H "Content-Type: application/json" \
https://quay.io/api/v1/organization/orgname/robots/myrobot \
-d '{
"description": "CI/CD robot account"
}'
# Grant permissions to robot
curl -X PUT \
-H "Authorization: Bearer $QUAY_TOKEN" \
-H "Content-Type: application/json" \
https://quay.io/api/v1/repository/orgname/myapp/permissions/user/orgname+myrobot \
-d '{"role": "write"}'
Security Scanning
# Quay automatically scans images on push (Clair)
# Get vulnerability report via API
curl -H "Authorization: Bearer $QUAY_TOKEN" \
"https://quay.io/api/v1/repository/username/myapp/image/sha256:abc123.../security?vulnerabilities=true"
# Set security notification
curl -X POST \
-H "Authorization: Bearer $QUAY_TOKEN" \
-H "Content-Type: application/json" \
https://quay.io/api/v1/repository/username/myapp/notification/ \
-d '{
"event": "vulnerability_found",
"method": "webhook",
"config": {
"url": "https://example.com/webhook"
}
}'
Build Triggers
# Create build trigger via API
curl -X POST \
-H "Authorization: Bearer $QUAY_TOKEN" \
-H "Content-Type: application/json" \
https://quay.io/api/v1/repository/username/myapp/trigger/ \
-d '{
"service": "github",
"config": {
"build_source": "username/repo",
"dockerfile_path": "/Dockerfile"
}
}'
Mirror Repository
# Create repository mirror via API
curl -X POST \
-H "Authorization: Bearer $QUAY_TOKEN" \
-H "Content-Type: application/json" \
https://quay.io/api/v1/repository/username/myapp/mirror \
-d '{
"external_reference": "docker.io/library/nginx",
"external_registry_username": "dockeruser",
"external_registry_password": "dockerpass",
"sync_interval": 86400,
"sync_start_date": "2024-01-01T00:00:00Z",
"root_rule": {
"rule_kind": "tag_glob_csv",
"rule_value": ["latest", "1.*"]
}
}'
Registry Comparison
| Feature | Docker Hub | GHCR | ECR | GCR/AR | ACR | Harbor | Quay.io |
|---|---|---|---|---|---|---|---|
| Public Images | ✅ | ✅ | ✅ (ECR Public) | ❌ | ❌ | ✅ | ✅ |
| Private Images | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Vulnerability Scanning | ✅ (paid) | ✅ | ✅ | ✅ | ✅ | ✅ (Trivy) | ✅ (Clair) |
| Geo-Replication | ❌ | ❌ | ✅ | ✅ | ✅ (Premium) | ✅ | ✅ |
| RBAC | Basic | ✅ | ✅ (IAM) | ✅ (IAM) | ✅ (Azure AD) | ✅ | ✅ |
| Image Signing | ❌ | ❌ | ❌ | ✅ (Binary Auth) | ✅ | ✅ (Notary) | ✅ |
| Build Automation | ✅ | ✅ (Actions) | ❌ | ✅ (Cloud Build) | ✅ (Tasks) | ❌ | ✅ |
| Self-Hosted | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ (Quay Enterprise) |
| Pricing | Free tier | Free | Pay per storage | Pay per storage | Pay per storage | Open source | Free tier |
Authentication Best Practices
Credential Management
# Use credential helpers instead of storing passwords
# Docker Desktop includes credential helpers
# Configure credential store (macOS)
cat ~/.docker/config.json
{
"credsStore": "osxkeychain"
}
# Configure credential store (Linux)
{
"credsStore": "secretservice"
}
# Use credential helper with specific registry
{
"credHelpers": {
"gcr.io": "gcr",
"us.gcr.io": "gcr",
"123456789012.dkr.ecr.us-east-1.amazonaws.com": "ecr-login"
}
}
Service Accounts and Tokens
# Use service accounts for CI/CD, not personal credentials
# GitHub Actions - use GITHUB_TOKEN
# GitLab CI - use CI_JOB_TOKEN
# Jenkins - use credentials binding
# Rotate credentials regularly
# Set expiration on tokens
# Use minimal required permissions
# Example: Create short-lived token (GCP)
gcloud auth print-access-token
# Example: Create time-limited token (Docker Hub)
# Use Personal Access Tokens with expiration
Multi-Registry Authentication
# Authenticate to multiple registries
docker login docker.io
docker login ghcr.io
docker login gcr.io
docker login 123456789012.dkr.ecr.us-east-1.amazonaws.com
# Verify auth configuration
cat ~/.docker/config.json
# Use skopeo for parallel authentication
skopeo login docker.io -u user1
skopeo login ghcr.io -u user2
skopeo login harbor.example.com -u admin
Image Distribution Patterns
Multi-Registry Mirroring
# Use skopeo to mirror images across registries
# Create sync configuration
cat > sync.yml <<EOF
docker.io:
images:
library/nginx:
- latest
- 1.25-alpine
library/redis:
- latest
- 7-alpine
EOF
# Sync to multiple destinations
for registry in harbor.example.com quay.io/myorg; do
skopeo sync \
--src yaml --dest docker \
sync.yml \
$registry
done
# Automated mirror with cron
cat > /etc/cron.d/registry-mirror <<EOF
0 2 * * * root /usr/local/bin/mirror-registries.sh
EOF
Air-Gapped Environments
# Export images for air-gapped transfer
# Save images to tarball
docker save -o images.tar nginx:latest redis:latest postgres:latest
# Or use skopeo for OCI format
skopeo copy docker://nginx:latest oci:/tmp/nginx-oci
tar czf nginx-oci.tar.gz /tmp/nginx-oci
# Transfer to air-gapped environment
# Load images
docker load -i images.tar
# Or with skopeo
skopeo copy oci:/path/to/nginx-oci docker://private-registry/nginx:latest
Content Trust and Image Signing
# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1
# Push signed image
docker trust sign registry.example.com/myapp:1.0
# Verify signature on pull
docker pull registry.example.com/myapp:1.0
# Use Notary for advanced signing
notary init registry.example.com/myapp
notary publish registry.example.com/myapp
# Note: Docker Content Trust / Notary v1 is legacy. New work should use
# cosign (sigstore) or Notation (Notary v2) — see the cosign examples below.
# Verify with notary
notary list registry.example.com/myapp
# Use cosign (sigstore) for keyless signing
cosign sign registry.example.com/myapp:1.0
# Verify with cosign
cosign verify registry.example.com/myapp:1.0
Quick Reference
Essential Docker Commands
# Login to registry
docker login [registry-url]
# Tag image
docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
# Push image
docker push IMAGE[:TAG]
# Pull image
docker pull IMAGE[:TAG]
# Logout
docker logout [registry-url]
Essential Skopeo Commands
# Inspect remote image
skopeo inspect docker://IMAGE
# Copy between registries
skopeo copy docker://SRC docker://DEST
# Delete image
skopeo delete docker://IMAGE
# List tags
skopeo list-tags docker://REPOSITORY
# Login
skopeo login REGISTRY
# Sync repositories
skopeo sync --src docker --dest docker SOURCE DEST
Registry URLs
# Docker Hub
docker.io or omit registry
# GitHub Container Registry
ghcr.io
# Amazon ECR
123456789012.dkr.ecr.REGION.amazonaws.com
# ECR Public
public.ecr.aws
# Google Container Registry
gcr.io, us.gcr.io, eu.gcr.io, asia.gcr.io
# Google Artifact Registry
REGION-docker.pkg.dev/PROJECT-ID/REPOSITORY
# Azure Container Registry
REGISTRY_NAME.azurecr.io
# Quay.io
quay.io
# Harbor
harbor.example.com
Common Issues and Solutions
| Issue | Possible Cause | Solution |
|---|---|---|
unauthorized: authentication required |
Not logged in | Run docker login or skopeo login with credentials |
denied: requested access to the resource is denied |
Insufficient permissions | Check IAM/RBAC permissions; verify namespace/project access |
manifest unknown |
Image doesn't exist | Verify image name, tag, and registry URL |
TLS handshake timeout |
Network/firewall issue | Check network connectivity; verify registry URL |
Error response from daemon: Get https://registry:443/v2/: http: server gave HTTP response to HTTPS client |
Registry using HTTP not HTTPS | Add registry to insecure-registries in daemon.json or use HTTPS |
toomanyrequests: Rate limit exceeded |
Docker Hub rate limiting | Authenticate or use paid plan; implement registry mirror |
error pulling image configuration: download failed after attempts |
Network interruption | Retry pull; check network stability |
failed to register layer: devmapper: Thin Pool has insufficient free space |
Insufficient storage | Clean up images/containers; increase storage |
layer does not match expected SHA256 digest |
Corrupted layer | Re-pull image; check registry health |
Error saving credentials: error storing credentials - err: exit status 1, out: Cannot autolaunch D-Bus without X11 |
Credential helper issue | Configure credential store or use --password-stdin |
Debugging Registry Issues
# Test registry connectivity
curl -v https://registry.example.com/v2/
# Check Docker daemon logs
journalctl -u docker -f
# Enable debug logging
dockerd --debug
# Check authentication file
cat ~/.docker/config.json
# Test with skopeo (more detailed errors)
skopeo inspect --debug docker://registry.example.com/myapp:latest
# Verify TLS certificates
openssl s_client -connect registry.example.com:443
# Test with curl
curl -u username:password https://registry.example.com/v2/_catalog
Performance Tuning
# Increase concurrent downloads
# Edit /etc/docker/daemon.json
{
"max-concurrent-downloads": 10,
"max-concurrent-uploads": 10
}
# Use registry mirror for Docker Hub
{
"registry-mirrors": ["https://mirror.gcr.io"]
}
# Enable HTTP/2
{
"experimental": true,
"metrics-addr": "127.0.0.1:9323"
}
# Restart Docker daemon
systemctl restart docker
Security Best Practices
Image Security
# Scan images before deployment (docker scan was removed; use docker scout)
docker scout cves myapp:1.0
# Use Trivy for comprehensive scanning
trivy image myapp:1.0
# Use Grype for vulnerability detection
grype myapp:1.0
# Scan with Clair
clairctl report myapp:1.0
# Use minimal base images
FROM alpine:3.18
# or
FROM gcr.io/distroless/base-debian11
Registry Security
# Enforce HTTPS
# Never use HTTP for production registries
# Implement RBAC
# Use least privilege principle
# Separate read/write permissions
# Enable audit logging
# Track all image pushes/pulls
# Use private networks
# Place registries in private subnets
# Use VPC endpoints (AWS) or Private Service Connect (GCP)
# Implement network policies
# Restrict access to registry ports
# Regular security updates
# Keep registry software updated
# Monitor CVE databases
Access Control
# Use service accounts, not personal credentials
# Rotate credentials regularly
# Use short-lived tokens when possible
# Implement MFA for human access
# Example: Time-limited token (24 hours)
# GitHub PAT with expiration
# AWS temporary credentials with STS
# Audit access logs regularly
aws ecr describe-image-scan-findings \
--repository-name myapp \
--image-id imageTag=latest
Advanced Topics
OCI Image Specification
# OCI (Open Container Initiative) standardises container formats
# Inspect OCI image manifest
skopeo inspect --raw docker://nginx:latest
# Convert Docker image to OCI
skopeo copy \
docker://nginx:latest \
oci:/tmp/nginx:latest
# Build OCI-compliant image with buildah (bud is a deprecated alias for build)
buildah build -t myapp:1.0 .
buildah push myapp:1.0 oci:/tmp/myapp:1.0
Registry API v2
# List catalogue
curl https://registry.example.com/v2/_catalog
# List tags
curl https://registry.example.com/v2/myapp/tags/list
# Get manifest
curl https://registry.example.com/v2/myapp/manifests/latest
# Delete by digest
curl -X DELETE \
https://registry.example.com/v2/myapp/manifests/sha256:abc123...
# Check blob existence
curl -I https://registry.example.com/v2/myapp/blobs/sha256:abc123...
Multi-Architecture Images
# Build multi-arch images with buildx
docker buildx create --use
docker buildx build \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
-t myapp:latest \
--push \
.
# Inspect multi-arch manifest
docker manifest inspect nginx:latest
# Copy multi-arch images with skopeo
skopeo copy --all \
docker://source/myapp:latest \
docker://dest/myapp:latest
Registry Garbage Collection
# Docker Registry garbage collection
docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml
# Harbor garbage collection (via API)
curl -X POST \
"https://harbor.example.com/api/v2.0/system/gc/schedule" \
-H "authorization: Basic ..." \
-d '{"schedule": {"type": "Manual"}}'
# ECR lifecycle policy handles this automatically
# GCR/Artifact Registry - configure retention policies
Summary
Container registries are critical infrastructure for modern containerised applications. Key considerations:
- Choose registry based on requirements: public vs private, cloud vs self-hosted
- Use Skopeo for daemonless operations and registry-to-registry transfers
- Implement security scanning and vulnerability management
- Follow authentication best practices: service accounts, credential helpers, token rotation
- Enable geo-replication for global availability and disaster recovery
- Implement lifecycle policies to manage storage costs
- Use content trust and image signing for supply chain security
- Monitor and audit registry access and operations
For most use cases:
- Public open-source: Docker Hub or GHCR
- AWS environments: ECR
- GCP environments: Artifact Registry
- Azure environments: ACR
- Self-hosted enterprise: Harbor
- Advanced security requirements: Quay.io or Harbor with Notary