Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

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.

Container Registry EcosystemRegistry Componentsdocker pushpush imagespull imagespull imagesContainer RegistryDeveloperCI/CD PipelineKubernetesDocker HostBlob StorageAuthenticationVulnerabilityScannerRegistry API v2Container Registry EcosystemRegistry Componentsdocker pushpush imagespull imagespull imagesContainer RegistryDeveloperCI/CD PipelineKubernetesDocker HostBlob StorageAuthenticationVulnerabilityScannerRegistry API v2

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.

skopeo copyskopeo inspectskopeo deleteskopeo copyskopeo syncSource RegistryDestination RegistryImage MetadataRemove ImageLocal DirectoryMultiple Registriesskopeo copyskopeo inspectskopeo deleteskopeo copyskopeo syncSource RegistryDestination RegistryImage MetadataRemove ImageLocal DirectoryMultiple 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.

Kubernetesghcr.ioGitHub ActionsDeveloperKubernetesghcr.ioGitHub ActionsDeveloperPush codeBuild imagePush imagePull imageKubernetesghcr.ioGitHub ActionsDeveloperKubernetesghcr.ioGitHub ActionsDeveloperPush codeBuild imagePush imagePull 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.

docker pushdocker pushpullpullpullpull anonymousLocalECR PrivateECR PublicECSEKSEC2Anyonedocker pushdocker pushpullpullpullpull anonymousLocalECR PrivateECR PublicECSEKSEC2Anyone

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).

GCP Container RegistriespushpushpullpullmigrateArtifact RegistryRecommendedContainer RegistryLegacyLocalGKECloud RunGCP Container RegistriespushpushpullpullmigrateArtifact RegistryRecommendedContainer RegistryLegacyLocalGKECloud Run

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.io URLs 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 existing gcr.io URLs.

# 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.

HarborSecurityTrivy ScannerHarbor PortalHarbor CoreRegistry ServiceJob ServiceNotary ServerPostgreSQLRedisBlob StorageHarborSecurityTrivy ScannerHarbor PortalHarbor CoreRegistry ServiceJob ServiceNotary ServerPostgreSQLRedisBlob Storage

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