Migrate a QuayEcosystem deployment to QuayRegistry

Migrate a QuayEcosystem deployment to QuayRegistry, review supported configurations, and revert if needed.

Migrating QuayEcosystem to QuayRegistry

To migrate an existing QuayEcosystem to a QuayRegistry managed by the Project Quay Operator, add the migration label to the QuayEcosystem custom resource and wait for the new QuayRegistry to start. You can then verify the migration and delete the old QuayEcosystem.

Procedure
  1. Add "quay-operator/migrate": "true" to the metadata.labels of the QuayEcosystem.

    $ oc edit quayecosystem <quayecosystem_name>
    metadata:
      labels:
        quay-operator/migrate: "true"
  2. Wait for a QuayRegistry CR to be created with the same metadata.name as your QuayEcosystem. The QuayEcosystem CR is marked with the label "quay-operator/migration-complete": "true".

  3. After the status.registryEndpoint of the new QuayRegistry is set, access Project Quay and confirm that all data and settings were migrated successfully.

  4. If everything works correctly, you can delete the QuayEcosystem. Kubernetes garbage collection cleans up all old resources.

Supported QuayEcosystem configurations for migration

The Project Quay Operator reports errors in its logs and in status.conditions if migrating a QuayEcosystem component fails or is unsupported.

All unmanaged components should migrate successfully because no Kubernetes resources need to be adopted and all the necessary values are already provided in Project Quay’s config.yaml file.

Database

Ephemeral database not supported (volumeSize field must be set).

Redis

Nothing special needed.

External Access

Only passthrough Route access is supported for automatic migration. Manual migration required for other methods.

  • LoadBalancer without custom hostname: After the QuayEcosystem is marked with label "quay-operator/migration-complete": "true", delete the metadata.ownerReferences field from existing Service before deleting the QuayEcosystem to prevent Kubernetes from garbage collecting the Service and removing the load balancer. A new Service will be created with metadata.name format <QuayEcosystem-name>-quay-app. Edit the spec.selector of the existing Service to match the spec.selector of the new Service so traffic to the old load balancer endpoint will now be directed to the new pods. You are now responsible for the old Service; the Quay Operator will not manage it.

  • LoadBalancer/NodePort/Ingress with custom hostname: A new Service of type LoadBalancer will be created with metadata.name format <QuayEcosystem-name>-quay-app. Change your DNS settings to point to the status.loadBalancer endpoint provided by the new Service.

Clair

Nothing special needed.

Object Storage

QuayEcosystem did not have a managed object storage component, so object storage will always be marked as unmanaged. Local storage is not supported.

Repository Mirroring

Nothing special needed.

Reverting QuayEcosystem migration

To revert to the QuayEcosystem when migration to QuayRegistry fails or causes issues, delete the QuayRegistry and restore the Route to the original Service. You can then use the Project Quay deployment managed by the QuayEcosystem.

Note

If your QuayEcosystem was managing the PostgreSQL database, the migration process moves your data to a new PostgreSQL database managed by the Operator. Your old database is not changed or removed but Project Quay will no longer use it once the migration is complete. If there are issues during the data migration, the migration process exits and it is recommended that you continue with your database as an unmanaged component.

Procedure
  1. Delete the QuayRegistry using either the UI or kubectl:

    $ kubectl delete -n <namespace> quayregistry <quayecosystem-name>
  2. If external access was provided using a Route, change the Route to point back to the original Service using the UI or kubectl.

Migrate a standalone Red Hat Quay deployment to the Operator

Back up a standalone Red Hat Quay deployment and migrate registry content to Red Hat Quay on OpenShift Container Platform.

Backing up a standalone deployment of Project Quay

To back up a standalone Project Quay deployment before Operator migration, you can copy config.yaml, dump the database, and sync object storage blobs.

Procedure
  1. Back up the config.yaml of your standalone Project Quay deployment:

    $ mkdir /tmp/quay-backup
    $ cp /path/to/Quay/config/directory/config.yaml /tmp/quay-backup
  2. Create a backup of the database that your standalone Project Quay deployment is using:

    $ pg_dump -h DB_HOST -p 5432 -d QUAY_DATABASE_NAME -U QUAY_DATABASE_USER -W -O > /tmp/quay-backup/quay-database-backup.sql
  3. Install the AWS CLI if you do not have it already.

  4. Create an ~/.aws/ directory:

    $ mkdir ~/.aws/
  5. Obtain the access_key and secret_key from the config.yaml of your standalone deployment:

    $ grep -i DISTRIBUTED_STORAGE_CONFIG -A10 /tmp/quay-backup/config.yaml
    Example output:
    DISTRIBUTED_STORAGE_CONFIG:
        minio-1:
            - RadosGWStorage
            - access_key: ##########
              bucket_name: quay
              hostname: 172.24.10.50
              is_secure: false
              port: "9000"
              secret_key: ##########
              storage_path: /datastorage/registry
  6. Store the access_key and secret_key from the config.yaml file in your ~/.aws directory:

    $ touch ~/.aws/credentials
  7. Optional: Check that your access_key and secret_key are stored:

    $ cat > ~/.aws/credentials << EOF
    [default]
    aws_access_key_id = ACCESS_KEY_FROM_QUAY_CONFIG
    aws_secret_access_key = SECRET_KEY_FROM_QUAY_CONFIG
    EOF
    Example output:
    aws_access_key_id = ACCESS_KEY_FROM_QUAY_CONFIG
    aws_secret_access_key = SECRET_KEY_FROM_QUAY_CONFIG
    Note

    If the AWS CLI does not automatically collect the access_key and secret_key from the ~/.aws/credentials file, you can configure these by running aws configure and manually entering the credentials.

  8. In your quay-backup directory, create a bucket_backup directory:

    $ mkdir /tmp/quay-backup/bucket-backup
  9. Back up all blobs from the S3 storage:

    $ aws s3 sync --no-verify-ssl --endpoint-url https://PUBLIC_S3_ENDPOINT:PORT s3://QUAY_BUCKET/ /tmp/quay-backup/bucket-backup/
    Note

    The PUBLIC_S3_ENDPOINT can be read from the Project Quay config.yaml file under hostname in the DISTRIBUTED_STORAGE_CONFIG. If the endpoint is insecure, use http instead of https in the endpoint URL.

Using backed up standalone content to migrate to OpenShift Container Platform

To migrate backed-up standalone Project Quay content to OpenShift Container Platform, you can restore the database, apply a custom configuration bundle, and sync blobs to Object Bucket storage.

Prerequisites
  • Your standalone Project Quay data, blobs, database, and config.yaml have been backed up.

  • Project Quay is deployed on OpenShift Container Platform using the Project Quay Operator.

  • A QuayRegistry with all components set to managed.

Note

The procedure in this document uses the following namespace: quay-enterprise.

Procedure
  1. Scale down the Project Quay Operator:

    $ oc scale --replicas=0 deployment quay-operator.v3.6.2 -n openshift-operators
  2. Scale down the application and mirror deployments:

    $ oc scale --replicas=0 deployment QUAY_MAIN_APP_DEPLOYMENT QUAY_MIRROR_DEPLOYMENT
  3. Copy the database SQL backup to the Quay PostgreSQL database instance:

    $ oc cp /tmp/user/quay-backup/quay-database-backup.sql quay-enterprise/quayregistry-quay-database-54956cdd54-p7b2w:/var/lib/pgsql/data/userdata
  4. Obtain the database password from the Operator-created config.yaml file:

    $ oc get deployment quay-quay-app -o json | jq '.spec.template.spec.volumes[].projected.sources' | grep -i config-secret
    Example output:
          "name": "QUAY_CONFIG_SECRET_NAME"
    $ oc get secret quay-quay-config-secret-9t77hb84tb -o json | jq '.data."config.yaml"' | cut -d '"' -f2 | base64 -d -w0 > /tmp/quay-backup/operator-quay-config-yaml-backup.yaml
    $ cat /tmp/quay-backup/operator-quay-config-yaml-backup.yaml | grep -i DB_URI
    Example output:
    postgresql://QUAY_DATABASE_OWNER:PASSWORD@DATABASE_HOST/QUAY_DATABASE_NAME
  5. Execute a shell inside of the database pod:

    # oc exec -it quay-postgresql-database-pod -- /bin/bash
  6. Enter psql:

    bash-4.4$ psql
  7. Drop the database:

    postgres=# DROP DATABASE "example-restore-registry-quay-database";
    Example output:
    DROP DATABASE
  8. Create a new database and set the owner as the same name:

    postgres=# CREATE DATABASE "example-restore-registry-quay-database" OWNER "example-restore-registry-quay-database";
    Example output:
    CREATE DATABASE
  9. Connect to the database:

    postgres=# \c "example-restore-registry-quay-database";
    Example output:
    You are now connected to database "example-restore-registry-quay-database" as user "postgres".
  10. Create a pg_trgm extension of your Quay database:

    example-restore-registry-quay-database=# CREATE EXTENSION IF NOT EXISTS pg_trgm ;
    Example output:
    CREATE EXTENSION
  11. Exit the postgres CLI to re-enter bash-4.4:

    \q
  12. Set the password for your PostgreSQL deployment:

    bash-4.4$ psql -h localhost -d "QUAY_DATABASE_NAME" -U QUAY_DATABASE_OWNER -W < /var/lib/pgsql/data/userdata/quay-database-backup.sql
    Example output:
    SET
    SET
    SET
    SET
    SET
  13. Exit bash mode:

    bash-4.4$ exit
  14. Create a new configuration bundle for the Project Quay Operator.

    $ touch config-bundle.yaml
  15. In your new config-bundle.yaml, include all of the information that the registry requires, such as LDAP configuration, keys, and other modifications that your old registry had. Run the following command to move the secret_key to your config-bundle.yaml:

    $ cat /tmp/quay-backup/config.yaml | grep SECRET_KEY > /tmp/quay-backup/config-bundle.yaml
    Note

    You must manually copy all the LDAP, OIDC, and other information and add it to the /tmp/quay-backup/config-bundle.yaml file.

  16. Create a configuration bundle secret inside of your OpenShift cluster:

    $ oc create secret generic new-custom-config-bundle --from-file=config.yaml=/tmp/quay-backup/config-bundle.yaml
  17. Scale up the Quay pods:

    $ oc scale --replicas=1 deployment quayregistry-quay-app
    Example output:
    deployment.apps/quayregistry-quay-app scaled
  18. Scale up the mirror pods:

    $ oc scale --replicas=1 deployment quayregistry-quay-mirror
    Example output:
    deployment.apps/quayregistry-quay-mirror scaled
  19. Patch the QuayRegistry CRD so that it contains the reference to the new custom configuration bundle:

    $ oc patch quayregistry QUAY_REGISTRY_NAME --type=merge -p '{"spec":{"configBundleSecret":"new-custom-config-bundle"}}'
    Note

    If Project Quay returns a 500 internal server error, you might have to update the location of your DISTRIBUTED_STORAGE_CONFIG to default.

  20. Create a new AWS credentials.yaml in your /.aws/ directory and include the access_key and secret_key from the Operator-created config.yaml file:

    $ touch credentials.yaml
    $ grep -i DISTRIBUTED_STORAGE_CONFIG -A10 /tmp/quay-backup/operator-quay-config-yaml-backup.yaml
    $ cat > ~/.aws/credentials << EOF
    [default]
    aws_access_key_id = ACCESS_KEY_FROM_QUAY_CONFIG
    aws_secret_access_key = SECRET_KEY_FROM_QUAY_CONFIG
    EOF
    Note

    If the AWS CLI does not automatically collect the access_key and secret_key from the ~/.aws/credentials file, you can configure these by running aws configure and manually entering the credentials.

  21. Record the NooBaa’s publicly available endpoint:

    $ oc get route s3 -n openshift-storage -o yaml -o jsonpath="{.spec.host}{'\n'}"
  22. Sync the backup data to the NooBaa backend storage:

    $ aws s3 sync --no-verify-ssl --endpoint-url https://NOOBAA_PUBLIC_S3_ROUTE /tmp/quay-backup/bucket-backup/* s3://QUAY_DATASTORE_BUCKET_NAME
  23. Scale the Operator back up to 1 pod:

    $ oc scale --replicas=1 deployment quay-operator.v3.6.4 -n openshift-operators

    The Operator uses the custom configuration bundle provided and reconciles all secrets and deployments. Your new Project Quay deployment on OpenShift Container Platform contains all of the information that the old deployment had. You can pull all images.

Migrate Microsoft Entra ID OIDC from v1.0 to v2.0

Migrate Microsoft Entra ID OIDC from v1.0 to v2.0 using a dual-issuer cutover and multi-issuer configuration.

Dual-issuer cutover from Entra ID v1.0 to v2.0

To migrate from Microsoft Entra ID v1.0 to v2.0 without interrupting clients in Project Quay, you can set the v2.0 discovery endpoint and temporarily list both issuers in OIDC_ISSUERS.

Procedure
  1. Set OIDC_SERVER to the v2.0 endpoint (https://login.microsoftonline.com/<tenant-id>/v2.0/).

  2. Add both issuer URLs to OIDC_ISSUERS:

      OIDC_ISSUERS:
        - https://sts.windows.net/<tenant-id>/
        - https://login.microsoftonline.com/<tenant-id>/v2.0
  3. Update upstream clients to v2.0 tokens.

  4. After all clients use v2.0, remove the v1.0 issuer from OIDC_ISSUERS.

Configuring Microsoft Entra ID v2 and multi-issuer OIDC

To accept Microsoft Entra ID v2.0 tokens and On-Behalf-Of API flows in Project Quay, you can configure multi-issuer and multi-audience settings in your OIDC *_LOGIN_CONFIG block. This support enables Microsoft Entra ID v2.0 access tokens, dual v1.0 and v2.0 acceptance during migration, and On-Behalf-Of (OBO) API flows used by integrations such as Red Hat Developer Hub (RHDH).

Prerequisites
  • You have an Entra ID app registration for Project Quay with a client secret and redirect URIs for your Project Quay hostname.

  • You can edit the Project Quay config.yaml file or Operator configBundleSecret resource.

Procedure
  1. In the Azure Portal, open your Project Quay app registration and set requestedAccessTokenVersion to 2 in the app manifest. The field might appear as api.requestedAccessTokenVersion.

    For OBO flows, expose an API on the Project Quay app registration, for example api://quay-api, and grant the upstream application permission to that scope.

  2. Update your *_LOGIN_CONFIG block with the v2.0 discovery endpoint and multi-issuer settings. For example:

    AUTHENTICATION_TYPE: OIDC
    # ...
    AZURE_LOGIN_CONFIG:
      CLIENT_ID: <quay_app_client_id>
      CLIENT_SECRET: <quay_app_client_secret>
      OIDC_SERVER: https://login.microsoftonline.com/<tenant-id>/v2.0/
      SERVICE_NAME: Microsoft Entra ID
      OIDC_DISABLE_USER_ENDPOINT: true
      OIDC_ISSUERS:
        - https://sts.windows.net/<tenant-id>/
        - https://login.microsoftonline.com/<tenant-id>/v2.0
      OIDC_AUDIENCES:
        - <quay_app_client_id>
        - api://quay-api
      OIDC_ALLOWED_CLIENTS:
        - <quay_app_client_id>
        - <upstream_app_client_id>
      USE_PKCE: true
      PKCE_METHOD: "S256"
      PUBLIC_CLIENT: true
    # ...
  3. Restart your Project Quay deployment or reconcile the Operator so the updated configuration is applied.

    Note
    • Set OIDC_SERVER to the v2.0 endpoint. The v2.0 JWKS endpoint includes v1.0 signing keys, so one discovery URL supports both token versions.

    • If you set OIDC_ALLOWED_CLIENTS, include your Project Quay application’s own CLIENT_ID. Direct user logins set azp to the application’s client ID. Omit OIDC_ALLOWED_CLIENTS if you do not need to restrict OBO clients.

    • Do not request Microsoft Graph scopes such as openid profile email when you need tokens with a custom audience. Use application-specific scopes such as api://quay-api/registry.access instead.