πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Deployments advanced ⏱ 30 minutes K8s 1.28+

Red Hat Quay Registry Kubernetes

Deploy and manage Quay container registry on Kubernetes. Mirror policies, robot accounts, security scanning, and integration with OpenShift.

By Luca Berton β€’ β€’ πŸ“– 5 min read

πŸ’‘ Quick Answer: Red Hat Quay is a production-grade container registry with image scanning (Clair), RBAC, geo-replication, mirroring, and quota management. Deploy via the Quay Operator on OpenShift with QuayRegistry CR β€” it auto-manages Postgres, Redis, object storage, and horizontal scaling. Use Quay as your disconnected mirror registry after bootstrapping with mirror-registry.

The Problem

Production Kubernetes environments need more than a basic registry:

  • Image vulnerability scanning before deployment
  • Role-based access control per team and namespace
  • Geo-replication across sites for disaster recovery
  • Repository mirroring from upstream registries on schedule
  • Quota management to prevent storage abuse
  • Audit logging for compliance
  • High availability for production clusters depending on the registry 24/7
  • Build triggers and notification webhooks

The Solution

Architecture

graph TD
    subgraph Quay Cluster
        QO[Quay Operator] --> Q1[Quay Pod 1]
        QO --> Q2[Quay Pod 2]
        QO --> Q3[Quay Pod 3]
        QO --> CL[Clair Scanner]
        QO --> MR[Mirror Worker]
        Q1 --> PG[(PostgreSQL)]
        Q1 --> RD[(Redis)]
        Q1 --> S3[(Object Storage<br/>S3/Azure/GCS/NooBaa)]
    end
    
    subgraph Consumers
        OCP[OpenShift Clusters]
        K8S[Kubernetes Clusters]
        CI[CI/CD Pipelines]
    end
    
    Q1 --> OCP
    Q2 --> K8S
    Q3 --> CI
    
    style QO fill:#EE0000,color:white
    style CL fill:#FF9800,color:white

Deploy Quay Operator on OpenShift

# Install Quay Operator from OperatorHub
cat <<EOF | oc apply -f -
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: quay-operator
  namespace: openshift-operators
spec:
  channel: stable-3.12
  installPlanApproval: Automatic
  name: quay-operator
  source: redhat-operators
  sourceNamespace: openshift-marketplace
EOF

# Wait for operator
oc get csv -n openshift-operators | grep quay

Create QuayRegistry Instance

# quay-registry.yaml
apiVersion: quay.redhat.com/v1
kind: QuayRegistry
metadata:
  name: enterprise-registry
  namespace: quay-system
spec:
  configBundleSecret: quay-config-bundle
  components:
  - kind: clair
    managed: true
  - kind: postgres
    managed: true
  - kind: redis
    managed: true
  - kind: objectstorage
    managed: true        # Uses NooBaa/ODF on OpenShift
  - kind: horizontalpodautoscaler
    managed: true
  - kind: mirror
    managed: true
  - kind: monitoring
    managed: true
  - kind: tls
    managed: true        # Auto-generates TLS via OpenShift routes
  - kind: route
    managed: true
# Create namespace
oc new-project quay-system

# Apply the QuayRegistry
oc apply -f quay-registry.yaml

# Monitor deployment
oc get quayregistry -n quay-system -w

# Get the Quay route URL
oc get route -n quay-system
# NAME                          HOST
# enterprise-registry-quay      enterprise-registry-quay-quay-system.apps.cluster.example.com

External Storage Backend

For production, use external object storage instead of managed NooBaa:

# quay-config.yaml (ConfigBundle)
DISTRIBUTED_STORAGE_CONFIG:
  s3Storage:
    - S3Storage
    - host: s3.example.com
      port: 443
      s3_access_key: <access-key>
      s3_secret_key: <secret-key>
      s3_bucket: quay-storage
      storage_path: /datastorage/registry
DISTRIBUTED_STORAGE_DEFAULT_LOCATIONS:
  - s3Storage
DISTRIBUTED_STORAGE_PREFERENCE:
  - s3Storage
# Create config bundle secret
oc create secret generic quay-config-bundle \
  --from-file=config.yaml=quay-config.yaml \
  -n quay-system

# Update QuayRegistry to use external storage
# Set objectstorage.managed: false

Repository Mirroring

Automatically sync from upstream registries on schedule:

# In Quay config.yaml
FEATURE_REPO_MIRROR: true
REPO_MIRROR_INTERVAL: 30       # seconds between mirror checks
REPO_MIRROR_TLS_VERIFY: true

Configure via UI or API:

# Create a mirrored repository via API
curl -X POST "https://quay.example.com/api/v1/repository" \
  -H "Authorization: Bearer $QUAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "repository": "ubi9-ubi",
    "namespace": "mirrors",
    "visibility": "public",
    "description": "Mirror of registry.redhat.io/ubi9/ubi"
  }'

# Configure mirror settings
curl -X POST "https://quay.example.com/api/v1/repository/mirrors/ubi9-ubi/mirror" \
  -H "Authorization: Bearer $QUAY_TOKEN" \
  -d '{
    "external_reference": "registry.redhat.io/ubi9/ubi",
    "sync_interval": 86400,
    "sync_start_date": "2026-04-29T00:00:00Z",
    "root_rule": {"rule_kind": "tag_glob_csv", "rule_value": ["latest", "9.*"]},
    "is_enabled": true
  }'

Geo-Replication

Replicate images across multiple sites:

# In Quay config.yaml
FEATURE_STORAGE_REPLICATION: true
DISTRIBUTED_STORAGE_CONFIG:
  us-east:
    - S3Storage
    - host: s3-us-east.example.com
      s3_bucket: quay-east
  eu-west:
    - S3Storage
    - host: s3-eu-west.example.com
      s3_bucket: quay-west
DISTRIBUTED_STORAGE_DEFAULT_LOCATIONS:
  - us-east
  - eu-west
DISTRIBUTED_STORAGE_PREFERENCE:
  - us-east
  - eu-west

Image Security Scanning (Clair)

# Clair is auto-deployed when managed: true
# Check vulnerability report via API
curl -s "https://quay.example.com/api/v1/repository/myorg/myapp/manifest/sha256:abc.../security?vulnerabilities=true" \
  -H "Authorization: Bearer $QUAY_TOKEN" | jq '.data.Layer.Features[].Vulnerabilities'

# Or view in the UI: Repository β†’ Tags β†’ Security Scan column

Quota Management

# In Quay config.yaml
FEATURE_QUOTA_MANAGEMENT: true
DEFAULT_SYSTEM_REJECT_QUOTA_BYTES: 10737418240   # 10 GB per org
PERMANENTLY_DELETE_TAGS: true

Robot Accounts for Kubernetes

# Create a robot account for CI/CD pull access
curl -X PUT "https://quay.example.com/api/v1/organization/myorg/robots/ci-puller" \
  -H "Authorization: Bearer $QUAY_TOKEN" \
  -d '{"description": "CI/CD pull account"}'

# Create Kubernetes pull secret from robot token
oc create secret docker-registry quay-pull-secret \
  --docker-server=quay.example.com \
  --docker-username="myorg+ci-puller" \
  --docker-password="$ROBOT_TOKEN" \
  -n my-namespace

# Link to service account
oc secrets link default quay-pull-secret --for=pull -n my-namespace

Garbage Collection

# In Quay config.yaml
FEATURE_GARBAGE_COLLECTION: true
TAG_EXPIRATION_OPTIONS:
  - 1w
  - 2w
  - 4w
DEFAULT_TAG_EXPIRATION: 2w

Common Issues

QuayRegistry stuck in β€œProgressing”

Usually a storage issue. Check NooBaa status or external S3 connectivity: oc get noobaa -n openshift-storage.

Clair not scanning images

Clair needs to index the image first. Check Clair pods: oc logs -l app=clair -n quay-system. Ensure the Clair database has enough storage.

Mirror sync fails with auth errors

Create robot accounts or external credentials for the upstream registry in the Quay UI under Repository β†’ Mirror β†’ Sync Credentials.

Best Practices

  • Bootstrap with mirror-registry, graduate to Quay β€” use mirror-registry for first cluster, then deploy Quay on it
  • Use external PostgreSQL and S3 for production β€” managed components are single-instance
  • Enable Clair scanning β€” block vulnerable images with Quay’s security policy
  • Robot accounts per environment β€” separate pull/push credentials for dev, staging, prod
  • Enable geo-replication for multi-site β€” reduces pull latency and provides DR
  • Set quotas per organization β€” prevent runaway storage consumption
  • Enable garbage collection with tag expiration β€” keeps storage clean

Key Takeaways

  • Red Hat Quay is the production-grade registry for OpenShift disconnected environments
  • Quay Operator auto-manages all components: Postgres, Redis, Clair, storage, TLS
  • Repository mirroring auto-syncs from upstream registries on schedule
  • Geo-replication provides multi-site redundancy for image content
  • Clair scanner provides vulnerability detection before deployment
  • Robot accounts provide secure, scoped credentials for Kubernetes pull secrets
  • Graduate from mirror-registry to Quay after bootstrapping your first cluster
#quay #registry #openshift #container-images #enterprise
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens