|Index|Getting started with SUSE Private Registry
SUSE Private Registry

Getting started with SUSE Private Registry

Publication Date: 2026-09-22

Release notes

SUSE Private Registry is an on-premises container registry. It is designed for SUSE customers who need a container registry that works well with other SUSE services and products.

This document provides a high-level overview of the features, capabilities and limitations of SUSE Private Registry, and highlights important product updates.

1 Release 1.2.1

Security updates:

  • Updates Harbor 2.15.1 to 2.15.2 (Release Notes v2.15.2).

  • Validates blob-mount source projects and rejects tokens missing the iat claim.

  • Hardens cryptography usage and removes the unused SMTP package.

  • Disables and ignores tlog during Cosign signing checks and verification.

Component updates:

  • Updates Harbor 2.15.1 to 2.15.2 (Release Notes v2.15.2).

  • Updates Trivy to version 0.72.0 (Release Notes v0.72.0) and the Trivy adapter to version 0.38.0.

  • Upgrades Go to version 1.26.4.

  • Upgrades Harbor UI to Angular 21, Clarity v18, and Node.js v22.

  • Updates the registry source tag to v2.8.3-harbor.1 of the distribution project (Release Notes v2.8.3-harbor.1).

  • Replaces the gopkg.in/yaml.v2 package with github.com/goccy/go-yaml.

Key fixes:

  • Bumps the repository update_time on tag and artifact changes.

  • Fixes multiple UI and UX issues, including checkboxes, form alignment, dark theme rendering, and i18n localization across portal components.

  • Aligns pending job queue counts and improves form styling.

  • Uses the selected tag for the pull command copy function.

  • Makes the OpenAPI generator download URL and PIP_INDEX_URL configurable.

Container image updates:

  • private-registry/1.2/harbor-core:1.2.0 ➡ private-registry/1.2/harbor-core:1.2.1

  • private-registry/1.2/harbor-exporter:1.2.0 ➡ private-registry/1.2/harbor-exporter:1.2.1

  • private-registry/1.2/harbor-jobservice:1.2.0 ➡ private-registry/1.2/harbor-jobservice:1.2.1

  • private-registry/1.2/harbor-portal:1.2.0 ➡ private-registry/1.2/harbor-portal:1.2.1

  • private-registry/1.2/harbor-registry:1.2.0 ➡ private-registry/1.2/harbor-registry:1.2.1

  • private-registry/1.2/harbor-registryctl:1.2.0 ➡ private-registry/1.2/harbor-registryctl:1.2.1

  • private-registry/1.2/harbor-trivy-adapter:1.2.0 ➡ private-registry/1.2/harbor-trivy-adapter:1.2.1

Upgrade notes:

  • No breaking changes in this release.

2 Release 1.2.0

Security updates:

Component updates:

  • Updates k8s.io/client-go to 0.34.1.

  • Updates aws-sdk-go to 1.55.8.

  • Updates go-ldap to 3.4.11.

  • Updates Go to 1.25.9.

New features and performance:

  • Adds support for the Cosign v3 Bundle signature format.

  • Introduces an option to disable audit log recording to the database during initialization.

  • Enables pprof support and the ability to export the Harbor version via the Prometheus exporter binary.

  • Replaces the existing pull-through cache with a new proxy cache implementation.

  • Improves general performance through code refactoring (for example, by using strings.Builder and strings.CutPrefix).

Key fixes:

  • Implements a security fix to reject bearer tokens issued before project creation.

  • Fixes issues related to OpenID Connect (OIDC) integration for users with a single group.

  • Corrects errors in user and group search functionality.

  • Resolves various user interface (UI) issues, including an unwanted scrollbar in tag retention and issues with the "Copy Pull Button" when tags are undefined.

  • Adds support for both docker-compose v1 and docker-compose v2.

  • Calls the /v2/auth/token application programming interface (API) to get a bearer token for the Docker Hub adapter.

Container image updates:

  • private-registry/harbor-core:1.1.2 ➡ private-registry/1.2/harbor-core:1.2.0

  • private-registry/harbor-exporter:1.1.2 ➡ private-registry/1.2/harbor-exporter:1.2.0

  • private-registry/harbor-jobservice:1.1.2 ➡ private-registry/1.2/harbor-jobservice:1.2.0

  • private-registry/harbor-portal:1.1.2 ➡ private-registry/1.2/harbor-portal:1.2.0

  • private-registry/harbor-registry:1.1.2 ➡ private-registry/1.2/harbor-registry:1.2.0

  • private-registry/harbor-registryctl:1.1.2 ➡ private-registry/1.2/harbor-registryctl:1.2.0

  • private-registry/harbor-trivy-adapter:1.1.2 ➡ private-registry/1.2/harbor-trivy-adapter:1.2.0

Helm chart updates:

  • The chart is the version 1.2.x will be in oci://registry.suse.com/private-registry/1.2/private-registry-helm

  • Makes health probe timeoutSeconds and failureThreshold configurable via values.

  • Fixes extra environment variables for the exporter.

  • Installs PodDisruptionBudget resources when the replica count is greater than one.

Upgrade notes:

  • No breaking changes in this release.

3 Release 1.1.3

Security updates:

Key fixes:

  • Fixed SessionRegenerate arguments/lifetime and prevented background polling from artificially renewing session TTLs.

  • Fixes scanner application programming interface (API) issues and resolves an issue that occurs when editing distribution instances without credentials.

  • Calls the /v2/auth/token API to get a bearer token for the Docker Hub adapter.

  • Bumped Go to version 1.25.9 and upgraded the OpenTelemetry SDK and go-jose packages.

Container image updates:

  • private-registry/harbor-core:1.1.2 ➡ private-registry/harbor-core:1.1.3

  • private-registry/harbor-exporter:1.1.2 ➡ private-registry/harbor-exporter:1.1.3

  • private-registry/harbor-jobservice:1.1.2 ➡ private-registry/harbor-jobservice:1.1.3

  • private-registry/harbor-portal:1.1.2 ➡ private-registry/harbor-portal:1.1.3

  • private-registry/harbor-registry:1.1.2 ➡ private-registry/harbor-registry:1.1.3

  • private-registry/harbor-registryctl:1.1.2 ➡ private-registry/harbor-registryctl:1.1.3

  • private-registry/harbor-trivy-adapter:1.1.2 ➡ private-registry/harbor-trivy-adapter:1.1.3

  • suse/postgres:17.9 ➡ suse/postgres:17.10

  • suse/nginx:1.21 ➡ suse/nginx:1.27

Upgrade notes:

  • No breaking changes in this release.

4 Release 1.1.2

Security Updates:

  • CVE-2026-4404: Use of hard coded credentials allows attackers to use the default password and gain access to the Web UI, if not set during installation or upgrade.

Now if the HARBOR_ADMIN_PASSWORD is not set during the installation or upgrade, it will be generated randomly and stored in a Kubernetes secret. This change mitigates the risk of using a default password and enhances the security of the installation.

Upgrade Notes:

No breaking changes in this release.

5 Release 1.1.1

Security Updates:

Container Image Updates:

  • private-registry/harbor-core:1.1.0 ➡ private-registry/harbor-core:1.1.1

  • private-registry/harbor-exporter:1.1.0 ➡ private-registry/harbor-exporter:1.1.1

  • private-registry/harbor-jobservice:1.1.0 ➡ private-registry/harbor-jobservice:1.1.1

  • private-registry/harbor-portal:1.1.0 ➡ private-registry/harbor-portal:1.1.1

  • private-registry/harbor-registry:1.1.0 ➡ private-registry/harbor-registry:1.1.1

  • private-registry/harbor-registryctl:1.1.0 ➡ private-registry/harbor-registryctl:1.1.1

  • private-registry/harbor-trivy-adapter:1.1.0 ➡ private-registry/harbor-trivy-adapter:1.1.1

Upgrade Notes:

No breaking changes in this release.

6 Release 1.1.0

Security Updates:

Container Image Updates:

  • Update the base image bci/bci-micro:15.6 to bci/bci-micro:15.7

  • Updated images:

    • private-registry/harbor-valkey:8.0.6 ➡ suse/valkey:8.0.6

    • private-registry/harbor-db:2.13.2 (postgres 17) ➡ suse/postgres:17.6

    • private-registry/harbor-nginx:1.21 ➡ suse/nginx:1.21

Upgrade Notes:

Images are now tagged with the SUSE Private Registry version instead of the corresponding Harbor version. The change in image versioning scheme is handled by Helm when upgrading the installation using the chart:

  • private-registry/harbor-core:1.1.0

  • private-registry/harbor-exporter:1.1.0

  • private-registry/harbor-jobservice:1.1.0

  • private-registry/harbor-portal:1.1.0

  • private-registry/harbor-registry:1.1.0

  • private-registry/harbor-registryctl:1.1.0

  • private-registry/harbor-registryctl:1.1.0

No breaking changes in this release.

7 Release 1.0.2

Security updates:

Component updates:

  • (cherry-pick) Remove payload from config audit log

  • bump go and base images

  • [CP] Bump trivy to 0.69.3 & adapter to v0.35.1-rc.1 on release-2.13.0

  • fix(security): reject bearer tokens issued before project creation

  • update the GitHub Actions workflows to use the ubuntu-latest runner

  • Bump trivy-adapter v0.35.1 GA on release-2.13.0

Container image updates:

  • private-registry/harbor-core:2.13.2 ➡ private-registry/harbor-core:2.13.5

  • private-registry/harbor-exporter:2.13.2 ➡ private-registry/harbor-exporter:2.13.5

  • private-registry/harbor-jobservice:2.13.2 ➡ private-registry/harbor-jobservice:2.13.5

  • private-registry/harbor-portal:2.13.2 ➡ private-registry/harbor-portal:2.13.5

  • private-registry/harbor-registryctl:2.13.2 ➡ private-registry/harbor-registryctl:2.13.5

  • private-registry/harbor-trivy-adapter:0.33.2 ➡ private-registry/harbor-trivy-adapter:0.35.1

  • private-registry/harbor-db:2.13.2 (postgres 17) ➡ suse/postgres:17.10

  • private-registry/harbor-nginx:1.21 ➡ suse/nginx:1.27

  • private-registry/harbor-valkey:8.0.6 ➡ suse/valkey:8.0.9

Upgrade notes:

  • No breaking changes in this release.

8 Release 1.0.1

Security updates:

  • CVE-2025-55198: Helm may panic due to incorrect YAML content.

  • CVE-2025-55199: Helm charts with specific JSON schema values can cause memory exhaustion.

  • CVE-2025-54410: Moby versions before 25.0.13, when firewall reloads, Docker fails to re-create iptables rules isolating bridge networks. This allows any container to access all ports on any other container across different bridge networks on the same host and breaks network segmentation in multi-tenant environments (only --internal networks remain protected).

  • CVE-2025-29923: go-redis allows potential out of order responses when CLIENT SETINFO times out during connection establishment.

  • CVE-2025-54388: Moby versions 28.2.0–28.3.2 fails to re-create iptables rules after a firewall reloads. This exposes containers with localhost-published ports (e.g., 127.0.0.1:8080) to remote access via the Docker bridge, while unpublished ports remain protected; fixed in version 28.3.3.

  • GHSA-2464-8j7c-4cjm: go-viper’s map structure may leak sensitive information in logs when processing malformed data.

  • CVE-2025-8959: HashiCorp go-getter vulnerable to arbitrary read through a symlink attack.

  • CVE-2025-58058: github.com/ulikunitz/xz leaks memory when decoding a corrupted multiple LZMA archives.

  • CVE-2025-53547: Helm chart dependency updating with malicious Chart.yaml content and symlink can lead to code execution.

Bugs fixed:

  • Trivy: the correct version is shown when calling trivy version.

Container image updates:

  • Valkey updated from 8.0.2 ➡ 8.0.6.

Upgrade notes:

  • No breaking changes in this release.

9 Release 1.0

Key features:

  • SUSE Private Registry is based on Harbor 2.13.2

    • Integration with Model Spec for first-class handling of AI models

    • Enhanced audit logging

  • Predictable release cycle aligned with SUSE Rancher Prime. SUSE Private Registry will be updated every 4 months

  • Each release is supported by SUSE for 18 months from the date of release

    • 6 months of security and bug fix maintenance, followed by

    • 12 months of security-only maintenance

  • Can be used to mirror SUSE Application Collection

  • Supports SUSE Security as an external scanner

SUSE Private Registry includes all the features of Harbor:

  • On-premises private container image and OCI artifact registry

  • Web interface for administration

  • Role-based Access Control

  • Fine-grained project configuration for image and artifact storage

  • Mirroring and pull-through caching of upstream registries' artifacts

  • Image retention and garbage collection controls

  • Scanning images for security vulnerabilities with the Trivy scanner

  • Generate SBOMs for stored images

  • Content trust with Cosign (Notary is not included)

1 Introduction

1.1 What is SUSE Private Registry?

SUSE Private Registry (Private Registry) is an on-premises container registry. Private Registry is designed for SUSE customers who need a container registry that works well with other SUSE services and products.

1.2 What are SUSE Private Registry benefits?

Private Registry is based on the Harbor project and includes all its core features as well as added benefits. For example:

  • On-premises container registry. Private Registry is a locally hosted container registry with access to online SUSE registry services.

  • Security. Private Registry offers security considerations for containerized environments. It includes authentication, authorization and vulnerability scanning.

  • Deployment flexibility. You can install Private Registry on a Kubernetes environment such as SUSE Rancher Prime: RKE2. You can also deploy Private Registry with High Availability setup.

  • User management. Private Registry provides authentication and authorization mechanism with role-based access control (RBAC).

  • User interface. Besides a command-line interface, you can administer Private Registry via Web user interface.

1.3 How does SUSE Private Registry work?

Private Registry is delivered as Open Container Initiative (OCI) containers and is expected to be deployed on a Kubernetes cluster. Private Registry consists of the following containers:

  • harbor-core: the main component of the Harbor registry, responsible for handling core functionalities such as managing projects, repositories and user interactions.

  • harbor-db: the database container that stores all metadata related to images, users and configurations for the Harbor registry.

  • harbor-jobservice: a service that manages background jobs, such as image replication and scheduled tasks, ensuring efficient processing of operations within the registry.

  • harbor-nginx: the reverse proxy and load balancer that routes incoming requests to the appropriate Harbor services, providing a single entry point for users.

  • harbor-portal: the Web-based user interface that allows users to interact with the Harbor registry, manage images, and configure settings through a graphical interface.

  • harbor-registry: the container that serves as the actual image storage back-end, handling the storage and retrieval of container images.

  • harbor-registryctl: a command-line tool for managing the Harbor registry, allowing users to perform administrative tasks and configurations directly from the terminal.

  • harbor-trivy-adapter: a container that integrates the Trivy vulnerability scanner with Harbor, enabling automated security scanning of container images for vulnerabilities.

  • harbor-exporter: the container that exports Harbor metrics in a format that can be collected by Prometheus for monitoring and observability.

  • harbor-valkey: an in-memory key-value store.

After deployment, you can log in via Web user interface. After successful authentication and authorization, you can configure multiple aspects of the product, for example:

  • Configure global settings, such as setting the registry to read-only mode or restricting who can create projects.

  • Select an authentication method.

  • Add users when in database authentication mode and assign the system administrator role to other users.

  • Apply resource quotas to projects.

  • Set up the replication of images between Private Registry instances.

1.4 For more information

Refer to the following sources to obtain more details:

2 Requirements

This section describes the minimum platform prerequisites and recommended production sizing for SUSE Private Registry.

2.1 Prerequisites

  • A Kubernetes cluster in version 1.33, 1.34 or 1.35

  • Helm version 3.2.0 or higher

  • Persistent Volume (PV) provisioner support in your infrastructure

  • An active subscription for SUSE Private Registry

2.1.1 Which Kubernetes versions are supported?

Private Registry versions 1.0, 1.1 and 1.2 are currently validated on Kubernetes versions 1.33, 1.34 and 1.35.

Private Registry versionKubernetes versions

1.0

1.33, 1.34, 1.35

1.1

1.33, 1.34, 1.35

1.2

1.33, 1.34, 1.35

Versions outside 1.33 through 1.35 are not currently validated for this release.

2.2 Hardware and sizing recommendations

Use these values as a production starting point. Tune based on retention policy, image churn, scan concurrency and replication traffic.

2.2.1 Cluster Baseline

ScopeRecommended starting point

Worker nodes

3 worker nodes minimum

Node shape

4 vCPU and 16 GiB RAM per node minimum

Preferred node shape

8 vCPU and 32 GiB RAM per node for higher scan and push concurrency

These recommendations align with Rancher guidance for RKE2 Kubernetes. For details, see RKE2 Kubernetes installation requirements. For guidance about distributing replicas, selecting reliable worker nodes, and planning external dependencies, see Chapter 6, High Availability setup.

2.2.2 Persistent Storage Baseline

ComponentDefault chart sizeRecommended starting size

Registry data

5 Gi

500 Gi to 1 Ti

Trivy cache and DB

5 Gi

20 Gi to 50 Gi

Jobservice logs

1 Gi

10 Gi

Internal PostgreSQL (if used)

1 Gi

20 Gi minimum

Internal Valkey (if used)

1 Gi

20 Gi minimum

The chart defaults are installation-oriented and should not be used as long-term production capacity values.

3 Installation using the Rancher UI

To install SUSE Private Registry using the Rancher UI, you must meet the following requirements and follow the steps below.

3.1 What requirements do I need to meet?

3.2 What are the steps to install SUSE Private Registry using the Rancher UI?

STEP 1: Tell Rancher where the SUSE Private Registry repository is located to look for the installation chart.
  1. Log in to Rancher.

  2. Click the three-line menu (☰) in the top-left corner, select Cluster Management, and click your cluster’s name, usually local.

  3. From the left-hand menu, select Apps › Repositories.

  4. Click the Create button in the top right and complete the form that opens:

    1. Target: Select OCI Repository.

    2. Name: Enter a name for the repository, such as SUSE Private Registry.

    3. Description: Optionally, add a description of the repository.

    4. OCI Repository Host URL: Enter `oci://registry.suse.com/private-registry/private-registry-helm`.

    5. Authentication: Change to Create an HTTP Basic Auth Secret and enter the user name and password of the registry’s credentials.

  5. Confirm with Create.

A screenshot showing how to add a SUSE Private Registry repository to Rancher
Figure 3.1: Adding a SUSE Private Registry repository
STEP 2: Create a secret to access the images in the `registry.suse.com`.
  1. Click the three-line menu (☰) in the top-left corner and select Cluster Management.

  2. Switch to the cluster to which you want to add the secret and click Explore.

  3. To navigate to secrets management, select Storage › Secrets and click Create in the top right.

  4. Select the HTTP Basic Auth secret and then the private-registry namespace.

  5. Enter suse-registry as the name for the secret.

  6. Fill the username and the password fields with the SUSE credentials obtained in Section 3.1, “What requirements do I need to meet?”.

A screenshot showing how to add SUSE Private Registry secrets to Rancher
Figure 3.2: Adding SUSE Private Registry secrets
STEP 3: Install the Helm chart.
  1. From the main left menu, select Apps › Charts.

  2. Enter the name of the assigned SUSE Private Registry repository in the search box. For example, SUSE Private Registry. If it does not appear in the list, click Refresh all repositories.

  3. Click the chart and view the README.md.

  4. Optionally, you can customize the installation values. Either click through the sections on the left side of the Edit Options panel to view all the values you can configure, or edit the values directly in the chart’s YAML file.

  5. In the top right corner, click Install this version.

A screenshot showing the installation screen of SUSE Private Registry in Rancher
Figure 3.3: Installing SUSE Private Registry

4 Installation using the command line

The following procedures describe how to deploy SUSE Private Registry (Private Registry) on a Kubernetes cluster.

4.1 Obtaining Kubernetes secrets from the SUSE Customer Center

To download and install the Private Registry images from SUSE Registry, you need a Kubernetes secret with SUSE Customer Center (SCC) mirroring credentials. To obtain the credentials from SCC, follow these steps:

  1. Visit SUSE Customer Center at https://scc.suse.com and log in.

  2. Select the organization with an active Private Registry subscription from the left sidebar.

  3. Select Proxies in the top menu. The credentials are displayed in the top right corner.

  4. To see the password, click the 'eye' icon.

  5. Create a password.txt file containing the obtained password.

    > head -1 ./password.txt | helm registry login registry.suse.com \
      --username <PRIVATE_REGISTRY_USERNAME> --password-stdin
  6. Create a namespace for SUSE Registry.

    > kubectl create namespace <PRIVATE_REGISTRY_NAMESPACE>
  7. Store the mirroring credentials retrieved from SCC as Kubernetes secrets by running the following command:

    > kubectl create secret docker-registry suse-registry \
      --namespace <PRIVATE_REGISTRY_NAMESPACE> \
      --docker-server=registry.suse.com \
      --docker-username=<PRIVATE_REGISTRY_USERNAME> \
      --docker-password=$(head -1 ./password.txt)
  8. Optionally, to use TLS encrypted communication, create a TLS secret from your private key and certificate files.

    > kubectl create secret tls suse-registry-tls \
      --namespace <PRIVATE_REGISTRY_NAMESPACE> \
      --cert=<CERTIFICATE>.pem \
      --key=<PRIVATE_KEY>.pem

4.2 Installing and running Private Registry using Helm

The following procedure describes how to install Private Registry using Helm. Before you begin, replace the following placeholders:

  • <RELEASE_NAME> with your custom release name for the Helm chart deployment.

  • <APP_VERSION> with the desired application version (for example, 1.2).

    1. Log in to SUSE Registry using the obtained SCC mirroring credentials.

      > head -1 ./password.txt | helm registry login registry.suse.com \
        --username <SUSE_REGISTRY_USERNAME> --password-stdin
    2. Install the latest version of the Private Registry Helm chart.

      > helm install <RELEASE_NAME> \
        oci://registry.suse.com/private-registry/<APP_VERSION>/private-registry-helm \
        --namespace <PRIVATE_REGISTRY_NAMESPACE>

      When you install the Private Registry Helm chart, it displays the following output:

      NOTES:
      CHART VERSION: 1.1.6
      It may take several minutes for the SUSE Private Registry  1.1.2 deployment to complete.
      
      Once the deployment has finished, you will be able to open the SUSE Private Registry portal at https://core.harbor.domain
      
      To get the admin credentials, copy and run the following commands:
      
      echo Username: "admin"
      echo Password: $(kubectl get secret --namespace <PRIVATE_REGISTRY_NAMESPACE>-harbor-core <RELEASE_NAME> -o jsonpath="{.data.HARBOR_ADMIN_PASSWORD}" | base64 -d)

      With Helm version 4, you can use a digest to specify the chart to install:

      > helm install <RELEASE_NAME> \
        oci://registry.suse.com/private-registry/<APP_VERSION>/private-registry-helm@sha256:<DIAGEST_OF_CHART_TO_INSTALL> \
        --namespace <PRIVATE_REGISTRY_NAMESPACE>
Tip
Tip
  • To get the password for the administrator user, you can also run the following command:

    > kubectl get secret \
      --namespace <PRIVATE_REGISTRY_NAMESPACE> \
      --harbor-core <RELEASE_NAME> \
      -o jsonpath="{.data.HARBOR_ADMIN_PASSWORD}" | base64 -d; echo
  • It is possible to use kstatus watcher with the flag '--wait=watcher' to make sure that all the objects are ready to finish the installation. Using the watcher may cause the command to take longer.

To override the default installation with custom values from the suse_registry_override.yaml file, refer to Appendix A, Overriding the SUSE Private Registry Helm chart.

The command starts deploying several related containers and may take several minutes to complete. It also prints a message with the URL to the Private Registry Web portal and commands to obtain the administrator credentials.

4.3 Upgrading Private Registry

To upgrade the release of the Helm chart to a specific newer version, run the following command:

> helm upgrade <RELEASE_NAME> \
  oci://registry.suse.com/private-registry/<APP_VERSION>/private-registry-helm \
  --version <NEW_VERSION_OF_HELM_CHART> \
  --namespace <PRIVATE_REGISTRY_NAMESPACE> \

The digest is only possible with Helm 4.

> helm upgrade <RELEASE_NAME> \
  oci://registry.suse.com/private-registry/<APP_VERSION>/private-registry-helm@sha256:<DIAGEST_OF_CHART_TO_INSTALL> \
  --namespace <PRIVATE_REGISTRY_NAMESPACE> \

5 Installing in air-gapped environment

SUSE Private Registry and Hauler provide a robust solution for managing content in disconnected environments while significantly simplifying the air-gapping workflow for Kubernetes distributions like RKE2 and K3s.

5.1 SUSE Private Registry and Hauler

5.1.1 About Hauler

  • Hauler: Purpose-built and lightweight, Hauler is a CLI tool that streamlines the management of content in air-gapped environments. It is engineered as a versatile utility for workflows and pipelines, allowing users to transport charts, container images, and other OCI artifacts into isolated networks without imposing a rigid workflow. For more information, refer to the Hauler documentation.

5.1.2 Prerequisites

Before beginning the air-gapped installation process, ensure you have the following in place:

  • Hauler CLI: Install Hauler on your source machine (connected to the Internet) and air-gapped system. Visit the Hauler Installation Guide for installation instructions.

  • Helm 3.x or later: Required for managing Helm charts and authentication with registries.

  • kubectl: Needed for deploying to your Kubernetes cluster.

  • Docker or container runtime: Required to handle OCI image operations (optional if using Hauler in stand-alone mode).

Registry Credentials:

  • Credentials for registries you want to pull from (e.g., registry.suse.com for SUSE images). For instructions on how to obtain these credentials, see Section 4.1, “Obtaining Kubernetes secrets from the SUSE Customer Center”.

  • For the target Private Registry instance, set the administrator password during installation using the harborAdminPassword Helm value. You will use this password to authenticate after deployment.

5.1.3 Configuration

An easy way to move artifacts into an air-gapped environment is to use a manifest, which is a YAML file with all the artifacts you want to transfer.

  • Reproducibility: The same manifest can be used consistently across multiple environments

  • Version control: Manifest files can be tracked in version control systems (e.g., Git) to ensure artifact versions are documented and auditable

  • Flexibility: Support for multiple artifact types including Helm charts, OCI images, and arbitrary files

  • Scalability: Easily manage large numbers of artifacts without manual configuration

5.1.3.1 Understanding Hauler Manifest Structure

The Hauler manifest supports three main kinds of artifacts:

  • Charts: Helm charts that will be deployed in your air-gapped environment. You specify the registry URL (OCI registry), chart name, and version.

  • Files: Arbitrary files or binaries from HTTP(S) sources, such as installation scripts or Kubernetes binaries.

  • Images: Container images (OCI images) that your applications and SUSE Private Registry depend on.

Each section in the manifest is independent and can be organized logically based on your deployment needs.

5.1.3.2 Example of hauler-manifest.yaml

Before using the following example, replace the placeholders:

  • <APP_VERSION> with the desired application version (for example, 1.2), as in Section 4.2, “Installing and running Private Registry using Helm”.

  • <CHART_VERSION> with the exact chart version you want to pull (for example, 1.2.19).

  • <RKE2_VERSION_URLENCODED> with the RKE2 release you want to install, found on the RKE2 releases page (for example, v1.34.5+rke2r1 written as v1.34.5%2Brke2r1, the percent-encoded form used in the download URL).

  • <HELM_VERSION> with the Helm release you want to install, found on the Helm releases page (for example, v3.18.0).

Important
Important

The Images list must use the exact image tags the chart’s own values.yaml defaults expect, not a hand-copied or simplified list. Get the authoritative list, already formatted as ready-to-use image references, by running:

> helm show values oci://registry.suse.com/private-registry/<APP_VERSION>/private-registry-helm \
  --version <CHART_VERSION> \
  | grep -E "repository:|tag:" | paste - - | awk '{print "registry.suse.com/"$2":"$4}'

For example, against chart version 1.2.19, this prints:

registry.suse.com/suse/nginx:1.27-21.8
registry.suse.com/private-registry/1.2/harbor-portal:1.2.1-1.118
registry.suse.com/private-registry/1.2/harbor-core:1.2.1-1.109
registry.suse.com/private-registry/1.2/harbor-jobservice:1.2.1-1.107
registry.suse.com/private-registry/1.2/harbor-registry:1.2.1-1.109
registry.suse.com/private-registry/1.2/harbor-registryctl:1.2.1-1.109
registry.suse.com/private-registry/1.2/harbor-trivy-adapter:1.2.1-1.118
registry.suse.com/suse/postgres:17.10-83.19
registry.suse.com/suse/valkey:8.0.10-17.8
registry.suse.com/private-registry/1.2/harbor-exporter:1.2.1-1.109

Copy each line as-is into the manifest’s Images.spec.images[].name field.

A manifest built from the wrong tags still lets hauler store sync and helm install succeed, but every pod fails ImagePullBackOff once you are air-gapped, with no route back to the internet to self-correct.

apiVersion: content.hauler.cattle.io/v1
kind: Charts
metadata:
  name: suse-private-registry-chart
spec:
  charts:
    - name: private-registry-helm
      repoURL: oci://registry.suse.com/private-registry/<APP_VERSION>
      version: <CHART_VERSION>
---
apiVersion: content.hauler.cattle.io/v1
kind: Files
metadata:
  name: rke2-install-files
spec:
  files:
    - path: https://get.rke2.io
      name: install-rke2.sh
    - path: https://get.helm.sh/helm-<HELM_VERSION>-linux-amd64.tar.gz
      name: helm-<HELM_VERSION>-linux-amd64.tar.gz
    - path: https://github.com/rancher/rke2/releases/download/<RKE2_VERSION_URLENCODED>/rke2.linux-amd64.tar.gz
      name: rke2.linux-amd64.tar.gz
    - path: https://github.com/rancher/rke2/releases/download/<RKE2_VERSION_URLENCODED>/rke2-images.linux-amd64.tar.zst
      name: rke2-images.linux-amd64.tar.zst
    - path: https://github.com/rancher/rke2/releases/download/<RKE2_VERSION_URLENCODED>/sha256sum-amd64.txt
      name: sha256sum-amd64.txt
---
apiVersion: content.hauler.cattle.io/v1
kind: Images
metadata:
  name: spr-images
spec:
  images:
    # SUSE Private Registry (Harbor) images -- get the full, exact list with
    # the `helm show values` command above; do not hand-copy the example below
    - name: registry.suse.com/private-registry/<APP_VERSION>/harbor-core:<IMAGE_TAG>
    - name: registry.suse.com/private-registry/<APP_VERSION>/harbor-portal:<IMAGE_TAG>
    - name: registry.suse.com/private-registry/<APP_VERSION>/harbor-registry:<IMAGE_TAG>
    - name: registry.suse.com/private-registry/<APP_VERSION>/harbor-registryctl:<IMAGE_TAG>
    - name: registry.suse.com/private-registry/<APP_VERSION>/harbor-jobservice:<IMAGE_TAG>
    - name: registry.suse.com/private-registry/<APP_VERSION>/harbor-exporter:<IMAGE_TAG>
    - name: registry.suse.com/private-registry/<APP_VERSION>/harbor-trivy-adapter:<IMAGE_TAG>
    - name: registry.suse.com/suse/valkey:<IMAGE_TAG>
    - name: registry.suse.com/suse/postgres:<IMAGE_TAG>
    - name: registry.suse.com/suse/nginx:<IMAGE_TAG>
Note
Note

The manifest’s Images kind only needs to list SUSE Private Registry’s own images. RKE2’s own required images are already covered by the rke2-images.linux-amd64.tar.zst file downloaded above and are loaded separately (see the RKE2 air-gap installation documentation for the load methods and for the correct tarball to use with a non-default CNI plugin).

5.1.4 Synergy: Orchestrating Hauler with SUSE Private Registry

To maximize the utility of both products, adopt the following integration pattern:

  • Centralized Source Control: Store all hauler-manifest.yaml files in a version-controlled repository (e.g., Git). This ensures that the exact versions of images and charts used in your air-gapped environment are tracked and reproducible.

  • Hauler-to-Registry Sync: Instead of manually handling tarballs, utilize Hauler’s ability to copy directly from the Hauler store to your SUSE Private Registry instance. This maintains the OCI structure and simplifies provenance tracking.

  • Pipeline Automation: Treat the Hauler sync process as a CI job. Automate the retrieval of upstream artifacts and push them into a designated "incoming" project within your SUSE Private Registry.

  • Security Scanning: Since SUSE Private Registry is based on Harbor, configure automatic vulnerability scanning on the projects where Hauler pushes content. This ensures that even in disconnected environments, your images remain secure.

5.1.5 Workflow

5.1.5.1 About Hauler Store

The Hauler store is a local directory that acts as an intermediate storage for all artifacts collected from registries and remote sources. Think of it as a staging area where Hauler downloads and organizes all content before you transport it to the air-gapped environment. The store maintains the OCI structure, making it easy to load artifacts into your SUSE Private Registry or local registries later.

5.1.5.2 Step-by-Step Instructions

Prerequisites:

  • Establish secure authentication with the Helm and Hauler registries to enable artifact access:

# Log in to SUSE registry to pull SUSE Private Registry images and charts
> helm registry login registry.suse.com -u <YOUR_USERNAME> -p <YOUR_PASSWORD> 1

# Configure Hauler with the same credentials
> head -1 ./password.txt | hauler login registry.suse.com -u <YOUR_USERNAME> --password-stdin

# or
# hauler login registry.suse.com -u <YOUR_USERNAME> -p <YOUR_PASSWORD>


# If using additional registries, log in to them as well
# hauler login <OTHER_REGISTRY> -u <YOUR_USERNAME> -p <YOUR_PASSWORD>

1

Replace <YOUR_USERNAME> and <YOUR_PASSWORD> with your actual SUSE Registry credentials.

  1. Create and prepare your hauler-manifest.yaml file on your internet-connected machine. Update the manifest to match your specific environment:

    • Update the chart version to the exact version you need for SUSE Private Registry

    • Adjust RKE2 versions if you’re using a different Kubernetes version (current example uses v1.34.5)

    • Add or remove images based on your specific requirements

    • If deploying additional applications, add their charts and images to the manifest

  2. Synchronize and package the artifacts:

    # Sync artifacts from remote registries into the Hauler store
    # This downloads all charts, images, and files specified in your manifest
    > hauler store sync -f hauler-manifest.yaml
    
    # Generate a portable tarball for your target architecture
    # Available architectures: amd64, arm64
    # This creates a compressed file that can be easily transported
    > hauler store save --filename hauler-store.tar.zst

    The hauler store save command creates a compressed tarball containing all collected artifacts. The resulting file size depends on the artifacts in your manifest (typically 20-50GB for a full RKE2 + SUSE Private Registry deployment).

  3. Transport the Hauler store tarball to your isolated air-gapped infrastructure using your preferred method:

    • External storage (USB drives, external hard drives)

    • Sneakernet (physical transport)

    • Direct file transfer if there’s a one-way network connection to the air-gapped environment

  4. In the air-gapped environment, load the artifacts into your SUSE Private Registry. Loading the store and pushing it to a registry are two separate steps:

    # Load the Hauler store archive (no manual extraction needed; `store load`
    # reads the .tar.zst directly)
    > hauler store load -f hauler-store.tar.zst
    
    # Create a file containing the password configured with `harborAdminPassword`
    # during the SUSE Private Registry installation
    > read -r -s -p "Enter the registry administrator password: " REGISTRY_PASSWORD; printf '\n'; printf '%s\n' "$REGISTRY_PASSWORD" > ./registry-password.txt
    
    # Log in to your target SUSE Private Registry
    > head -1 ./registry-password.txt | hauler login <SUSE_PRIVATE_REGISTRY_HOST> \\ 1
      -u <REGISTRY_USERNAME> --password-stdin 2
    
    # Push the loaded store's content into your SUSE Private Registry project
    > hauler store copy registry://<SUSE_PRIVATE_REGISTRY_HOST>/<PROJECT_NAME>
    
    # If your SUSE Private Registry is served over unencrypted HTTP, add --plain-http:
    # hauler store copy registry://<SUSE_PRIVATE_REGISTRY_HOST>/<PROJECT_NAME> --plain-http

    1

    Replace <SUSE_PRIVATE_REGISTRY_HOST> with the host name of your SUSE Private Registry instance, without https:// or http:// (for example, registry.example.com).

    2

    Replace <REGISTRY_USERNAME> with your SUSE Private Registry administrator user name. The password in registry-password.txt must match the password configured with harborAdminPassword.

  5. Alternatively, use Hauler’s ephemeral local registry for immediate deployment:

    To deploy directly without waiting for a full SUSE Private Registry setup, you can use Hauler’s local registry. Hauler’s registry serves plain, unencrypted HTTP, so Kubernetes needs to be told to trust it before you deploy, and the chart itself is served under a path based on its own Chart.yaml name (suse-private-registry), not the manifest’s chart alias or its original registry path:

    # On the air-gapped host, add a plain-HTTP mirror for the Hauler registry so
    # containerd does not attempt an HTTPS handshake against it, then restart
    # RKE2 (or K3s) to pick up the change:
    > cat <<EOF | sudo tee /etc/rancher/rke2/registries.yaml
    mirrors:
      "localhost:5000":
        endpoint:
          - "http://localhost:5000"
    EOF
    > sudo systemctl restart rke2-server
    
    # Start Hauler's ephemeral registry (runs on localhost:5000)
    > hauler store serve registry --port 5000 &
    
    # Deploy SUSE Private Registry using the local Hauler registry
    # Note: This registry is temporary and will be removed after the process ends
    > helm install <RELEASE_NAME> \
      oci://localhost:5000/hauler/suse-private-registry \
      --namespace <PRIVATE_REGISTRY_NAMESPACE> --create-namespace \
      --version <CHART_VERSION> \
      --plain-http \
      --set global.imageRegistry=localhost:5000 \
      --set-file harborAdminPassword=./registry-password.txt \
      --set-json 'imagePullSecrets=[]'

    After a successful deployment, you can configure and use SUSE Private Registry as your persistent artifact registry.

5.1.5.3 Verification and Troubleshooting

After completing the deployment:

  1. Verify that all artifacts have been successfully loaded:

    # Check if images are available in your SUSE Private Registry
    # Log into the registry web interface or use the Registry API to verify image presence
  2. Verify the SUSE Private Registry deployment:

    # Check the status of SUSE Private Registry pods
    > kubectl get pods -n <PRIVATE_REGISTRY_NAMESPACE>
  3. Test connectivity and functionality:

    # Verify that you can log in and pull images from the registry.
    # Note: Hauler uses Docker credentials. If you completed the previous steps
    # and pushed images, you are already authenticated.
    > docker login <SUSE_PRIVATE_REGISTRY_HOST> -u <REGISTRY_USERNAME>
    > docker pull <REGISTRY_URL>/library/image:tag

6 High Availability setup

You can use Helm to deploy the highly available (HA) Private Registry on a Kubernetes cluster. An HA deployment reduces interruptions when a pod or worker node becomes unavailable. It does not protect the registry from every failure. The database, cache, image storage, Ingress endpoint, and Kubernetes cluster must also remain available.

This topic describes the Private Registry-specific choices and trade-offs. For Kubernetes control-plane and worker-node design, see the Kubernetes production environment documentation. For Rancher-managed clusters, see the Rancher production installation checklist.

6.1 Understanding the HA setup

Most Private Registry application components are stateless. You can run multiple replicas and distribute them across worker nodes or failure domains. This protects the application from a pod or node failure, but only when the replicas do not share the same failure domain.

The stateful parts of the deployment require separate resilience decisions:

  • PostgreSQL stores registry metadata, projects, users, and configuration.

  • Valkey or Redis stores sessions, queues, and cache data.

  • Image and chart storage holds the registry content.

  • Persistent volumes hold database data, job logs, and scanner data when the corresponding chart settings use PVCs.

  • The Ingress controller, load balancer, DNS, and TLS endpoint provide access to the registry.

Private Registry does not deploy or manage HA for these external endpoints and services.

6.2 Choosing a resilience model

Choose a model based on the kinds of failure that the deployment must tolerate:

ModelConfigurationTrade-off

Pod availability

Run two or more replicas of the stateless Private Registry components on separate worker nodes.

Protects against failures affecting pods or worker nodes, but does not protect the database, cache, storage, or Ingress endpoint.

Production availability

Combine distributed application replicas with HA PostgreSQL, HA Valkey or Redis, durable image storage, and a highly available Ingress endpoint.

Reduces more failure modes, but requires you to operate or purchase these services separately.

Disaster recovery

Keep backups in a separate environment and prepare a recovery Kubernetes cluster with compatible storage and networking.

Protects against cluster loss, but recovery is not automatic and has a defined recovery point and recovery time.

Increasing the replica count alone does not create a resilient deployment. For example, two replicas on the same worker node are both affected when that node fails.

6.3 Distributing Private Registry components

The example in Appendix B, Example of a Private Registry HA setup Helm chart sets three replicas for the portal, core, job service, and registry components, together with the corresponding nodeSelector and topologySpreadConstraints values. These settings are available for the Private Registry components in Appendix A, Overriding the SUSE Private Registry Helm chart.

Use the following scheduling principles:

  • Label a dedicated or reliable worker-node pool and select it with nodeSelector or node affinity.

  • Use pod anti-affinity or topology spread constraints to place replicas on different nodes or failure domains.

  • Reserve enough CPU and memory for another replica when one node is unavailable.

  • Use taints and tolerations when only selected workloads should run on the registry nodes.

  • Test node drains and upgrades with the planned replica count and storage configuration.

Kubernetes does not automatically spread replicas across failure domains unless the workload includes suitable scheduling rules. For more information, see the Kubernetes pod assignment and topology spread constraints documentation.

The chart can create a pod disruption budget for each component with <COMPONENT>.podDisruptionBudget.enabled, which is disabled by default. Node-drain policy remains a cluster-level control. Define it with the platform tools used by your organization and verify that the disruption budgets do not prevent planned maintenance.

Size the worker-node pool with at least one node more than the replica count. With whenUnsatisfiable: DoNotSchedule and a pool the same size as the replica count, draining a node leaves one replica of every component unschedulable until the node returns. The registry stays available on the remaining replicas, but the deployment runs without spare capacity for the duration of the maintenance.

Kubernetes cluster with Ingress in HA setup using HA PostgreSQL and HA Valkey
Figure 6.1: Private Registry HA setup

6.4 Providing a highly available endpoint

Use a highly available Ingress controller or load balancer in front of the Private Registry services. The endpoint provides the redundancy; DNS only needs a stable record that resolves reliably to it, and the TLS certificate only needs to remain valid when pods or worker nodes change. The externalURL value and expose.ingress.hosts.core value must identify the same registry address.

Private Registry does not manage the external endpoint. Configure health checks, endpoint failover, certificate renewal, and DNS availability with the Ingress or load-balancer provider.

When expose.type is ingress, the chart does not deploy its own nginx proxy, and increasing nginx.replicas has no effect. Endpoint redundancy then comes entirely from the Ingress controller. Increase nginx.replicas only when expose.type is clusterIP, nodePort, or loadBalancer.

6.5 Prerequisites

An HA deployment has the same base prerequisites as a standard deployment. For the supported Kubernetes versions, the required Helm version, and the subscription requirement, see Section 2.1, “Prerequisites”.

An HA deployment additionally requires the following:

  • Enough worker nodes and spare capacity to distribute the replicas and to keep the registry running while one node is unavailable

  • A highly available Ingress controller or load balancer with stable DNS and TLS

  • HA PostgreSQL 9.6+; Private Registry does not deploy or manage the HA database

  • HA Valkey or Redis; Private Registry does not deploy or manage HA Valkey or Redis

  • Persistent storage that remains available after a worker-node failure, or supported external object storage

  • SUSE Customer Center (SCC) mirroring credentials stored as a Kubernetes pull secret, as described in Section 4.1, “Obtaining Kubernetes secrets from the SUSE Customer Center”

6.6 Choosing resilient dependencies and storage

The following table summarizes the main dependency choices:

DependencyWhat it stores or providesRecommended production choiceFailure consideration

PostgreSQL

Registry metadata, users, projects, and configuration.

Use an HA external service or a database platform with tested failover and backups.

A database outage affects registry operations even when all registry pods are healthy. After a failover, verify that the components reconnect to the new primary; restart the affected deployments if they do not.

Valkey or Redis

Sessions, queues, and cache data.

Use an HA external service. Configure a supported direct or Sentinel connection in the chart. Cluster mode is not supported.

Users may lose sessions during recovery. Queued or in-progress tasks may need review.

Image and chart storage

Container images and Helm charts.

Use supported durable object storage or storage that survives node failure.

If image storage is unavailable, users cannot reliably push or pull artifacts.

Ingress and DNS

The external connection to the registry.

Use a redundant controller or load balancer with stable DNS and TLS.

An unavailable endpoint prevents access even when the application is running.

External managed services reduce the operational work inside the Kubernetes cluster, but introduce network, credential, and provider dependencies. In-cluster stateful services keep the deployment self-contained, but you must design their replication, storage, upgrades, monitoring, and backups. Private Registry does not provide those HA implementations.

For the supported connection and storage values, see Appendix B, Example of a Private Registry HA setup Helm chart and Appendix A, Overriding the SUSE Private Registry Helm chart.

When you use file system storage, confirm that the storage class can make the volume available after a node failure. Shared ReadWriteMany access is not automatically required for every deployment, but the access mode must match the selected chart configuration and storage implementation. When you use object storage, protect the object store with its own availability, access-control, retention, and backup policies.

To configure file system storage, set the storage class and access mode in the values file. For example:

persistence:
  enabled: true
  persistentVolumeClaim:
    registry:
      storageClass: <STORAGE_CLASS_NAME>
      accessMode: ReadWriteMany
      size: 500Gi

Use ReadWriteMany only when the storage implementation supports simultaneous access from the nodes that run the registry pods. Verify that the StorageClass supports the requested access mode and provides the required durability and failure behavior before using it in production.

When the storage implementation does not support ReadWriteMany, you must set updateStrategy.type to Recreate. That strategy stops the running pods before it starts the new ones, which causes downtime during an upgrade and prevents the registry components from running more than one replica per volume. This applies even to a component that runs a single replica. With the default RollingUpdate strategy, the replacement pod is created before the running pod stops, and it stays Pending indefinitely: the ReadWriteOnce volume ties it to one node, and the topologySpreadConstraints of the component keep it off that node while the running pod is still there. Use object storage or a ReadWriteMany volume when the deployment must stay available during upgrades.

Select the storage class and the access mode before the first installation. The specification of a PersistentVolumeClaim cannot be changed afterwards, so an upgrade that modifies storageClass or accessMode for an existing release fails with spec is immutable after creation and leaves the release in a failed state. Recover from it with helm rollback. Only the size can be increased, and only when the storage class allows volume expansion. To move the registry to a different storage class or access mode, copy the data to the new volume, delete the claim, and let the chart create it again. Note that a claim created with persistence.resourcePolicy set to keep also survives helm uninstall and must be deleted explicitly. Installing the chart again with the same release name in the same namespace reuses the surviving claims and preserves their data.

Use the sizing recommendations in Chapter 2, Requirements as a starting point. Increase capacity based on artifact growth, scan activity, job logs, retention, and replication traffic.

6.7 Deploying Private Registry with HA

  1. Complete the SCC mirroring credential and Kubernetes secret steps described in Section 4.1, “Obtaining Kubernetes secrets from the SUSE Customer Center”.

  2. Log in to SUSE Registry using the SCC mirroring credentials.

    > head -1 ./password.txt | helm registry login registry.suse.com \
      --username <SUSE_REGISTRY_USERNAME> --password-stdin
  3. Create a suse_registry_override.yaml values file that matches your requirements. Refer to Appendix B, Example of a Private Registry HA setup Helm chart for an example values file for a Private Registry HA setup. Refer to Appendix A, Overriding the SUSE Private Registry Helm chart for a complete list of values to specify or override.

  4. Install the Private Registry Helm chart with your values file. Replace <APP_VERSION> with the application version to install, for example, 1.2. Replace <RELEASE_NAME> with your custom release name for the Helm chart deployment.

    > helm install <RELEASE_NAME> \
      oci://registry.suse.com/private-registry/<APP_VERSION>/private-registry-helm \
      --namespace <PRIVATE_REGISTRY_NAMESPACE> \
      -f suse_registry_override.yaml

6.8 Performing maintenance and upgrades

Follow this procedure for planned maintenance, such as a node drain or a rolling upgrade:

  1. Verify that the registry has enough available capacity to run during a node drain.

  2. Upgrade one failure domain at a time.

  3. After each change, verify pod readiness, registry API health, and image pulls and pushes. For example, log in and push and pull a test image to confirm that the registry serves traffic correctly:

    > docker login <SUSE_PRIVATE_REGISTRY_URL> -u <REGISTRY_USERNAME>
    > docker pull <SUSE_PRIVATE_REGISTRY_URL>/library/<TEST_IMAGE>:<TAG>
    > docker push <SUSE_PRIVATE_REGISTRY_URL>/library/<TEST_IMAGE>:<TAG>

Set persistence.resourcePolicy to keep when PVCs must remain after a Helm release is deleted, for example when a release is reinstalled as part of maintenance. This setting does not replace backups and does not protect PVCs from deletion outside Helm.

Before placing the deployment into production, test the failure of one pod and one worker node, and test the documented recovery procedure with a non-production backup.

6.9 Configuring retention and garbage collection

Retention and garbage-collection settings are standing configuration, not steps in the maintenance procedure in Section 6.8, “Performing maintenance and upgrades”. Set them up once, independently of any planned maintenance, and let them run on their own schedule.

Configure image retention policies and schedule garbage collection in the Private Registry administrator interface. Garbage collection removes unreferenced image data after retention policies or artifact deletions run. Review the reclaimable storage before a production garbage-collection run and monitor storage growth afterward. The registry upload-purging settings also remove leftover data from incomplete or interrupted image uploads (as opposed to garbage collection, which removes unreferenced but otherwise complete image data); see Appendix A, Overriding the SUSE Private Registry Helm chart for the available registry.upload_purging values.

6.10 Monitoring the deployment

Monitor the following conditions:

  • Available replicas for each Private Registry deployment.

  • Pending or restarting pods and PVCs that are not bound.

  • Database, cache, object-storage, and Ingress health.

  • Artifact storage growth, job queues, scan failures, and replication failures.

Enable the chart’s Prometheus metrics and configure a ServiceMonitor when your cluster runs the Prometheus Operator:

metrics:
  enabled: true
  serviceMonitor:
    enabled: true

Create alerts for unavailable replicas, repeated pod restarts, storage capacity, failed scans, failed jobs, and dependency health. Use metrics.serviceMonitor.additionalLabels when your monitoring stack requires a label to discover the ServiceMonitor.

6.11 Managing network traffic

Ensure that the worker-node and load-balancer networks provide enough bandwidth for peak image pushes, pulls, replication, and vulnerability database updates. Monitor network throughput and connection errors during normal and peak workloads.

If the registry requires a corporate proxy, configure the proxy URL, excluded addresses, and affected components in the Helm values:

proxy:
  httpProxy: <HTTP_PROXY_URL>
  httpsProxy: <HTTPS_PROXY_URL>
  noProxy: 127.0.0.1,localhost,.local,.internal
  components:
    - core
    - jobservice
    - trivy

Include the registry services, database, cache, object storage, Kubernetes API, and monitoring endpoints in noProxy when they must not use the proxy. Verify proxy access for each component that downloads external content or connects to an external service.

6.12 Planning backup and recovery

HA reduces downtime during selected failures. It does not replace backups or protect against accidental deletion, data corruption, or loss of the entire cluster. Define the required recovery point objective (RPO) and recovery time objective (RTO) before selecting backup schedules and storage.

For a complete recovery plan:

  • Back up PostgreSQL with an application-consistent database method.

  • Protect external image and chart storage with the provider’s backup and retention features.

  • Store Kubernetes resource and volume backups outside the primary cluster.

  • Preserve the chart version, application version, Helm values, connection information, and required secret references.

  • Prepare a recovery cluster with compatible Kubernetes versions, StorageClass names, networking, and Ingress configuration.

For the detailed Velero procedure, see Chapter 9, Backing up and restoring with Velero. The procedure also describes expected recovery behavior, including lost Valkey or Redis sessions, interrupted tasks, internal PostgreSQL consistency requirements, and the need to preserve chart-generated secrets.

6.13 Reviewing the deployment before production

Confirm the following items before you rely on the deployment for production workloads:

  • Replicas are distributed across worker nodes or failure domains.

  • Worker nodes have enough reserved capacity for a node failure.

  • The Ingress endpoint is redundant, and DNS reliably resolves to it.

  • The TLS certificate is valid on every endpoint instance and renews automatically before it expires.

  • PostgreSQL and Valkey or Redis have tested failover and backup procedures.

  • Image and chart storage remains available during a worker-node failure.

  • Monitoring detects unavailable replicas, dependency failures, storage exhaustion, and failed jobs.

  • A Velero backup, together with the external database and object-storage backups, has been restored successfully in a test environment. See Chapter 9, Backing up and restoring with Velero.

7 Configure Rancher as an OIDC Identity Provider

This guide explains how to configure Rancher to act as an OIDC Identity Provider, allowing users to authenticate into external applications such as SUSE Private Registry using their Rancher credentials.

7.1 Step 1: Enable the oidc-provider feature flag

  1. Log in to the Rancher UI as an administrator.

  2. Click the three-line menu (☰) in the upper left and go to Global Settings › Feature Flags.

  3. Find the oidc-provider flag, click the More Actions icon (⋮), and click Activate.

Configure Rancher as an OIDC Identity Provider

7.2 Step 2: Create an OIDCClient resource

Rancher uses an OIDCClient Custom Resource to register downstream applications.

  1. Create a file named rancher-oidc-client.yaml with the following content:

    apiVersion: management.cattle.io/v3
    kind: OIDCClient
    metadata:
      name: spr-client
    spec:
      tokenExpirationSeconds: 600
      refreshTokenExpirationSeconds: 3600
      redirectURIs:
        # Replace this with the actual callback URL of your SUSE private registry instance
        - "https://<SUSE_PRIVATE_REGISTR_URL>/c/oidc/callback"
  2. Apply the file to the cluster where Rancher is running:

    > kubectl apply -f rancher-oidc-client.yaml

7.3 Step 3: Retrieve the Client ID and Secret

Once the resource is created, Rancher automatically populates the clientID and provisions a Kubernetes Secret containing the clientSecret.

  1. Get the generated Client ID:

    > kubectl get oidcclient spr-client -o jsonpath="{.status.clientID}"
  2. Fetch the Client Secret. Remember to replace <YOUR_CLIENT_ID> with the ID retrieved in the previous step:

    > kubectl get secret <YOUR_CLIENT_ID>
      -n cattle-oidc-client-secrets
      -o jsonpath="{.data.client-secret-1}" | base64 -d

7.4 Step 4: Configure SUSE Private Registry

You can configure SUSE Private Registry to use Rancher as its OIDC provider by passing the values via Helm. Use the core.configureUserSettings block in your values-oidc.yaml configuration.

The following is an example block using your Rancher endpoint and the credentials retrieved above. Replace the values of <YOUR_CLIENT_ID> and <YOUR_CLIENT_SECRET>.

core:
  configureUserSettings: |
    {
      "auth_mode": "oidc_auth",
      "oidc_name": "Rancher",
      "oidc_endpoint": "<RANCHER_URL>/oidc",
      "oidc_client_id": "<YOUR_CLIENT_ID>",
      "oidc_client_secret": "<YOUR_CLIENT_SECRET>",
      "oidc_scope": "openid,profile,offline_access",
      "oidc_verify_cert": false,
      "oidc_auto_onboard": true,
      "oidc_user_claim": "preferred_username",
      "oidc_groups_claim": "groups",
      "oidc_admin_group": "spr-admins"
    }
Note
Note

Ensure oidc_verify_cert is set to false if your Rancher instance is using self-signed certificates. By specifying oidc_admin_group, any Rancher user belonging to the spr-admins group will automatically be granted System Administrator privileges in SUSE Private Registry.

8 Migrating data from upstream Harbor to SUSE Private Registry

8.1 Overview

SUSE Private Registry (Private Registry) is derived from Harbor and shares the same PostgreSQL database schema and registry blob storage layout. This means that data from an existing Harbor installation, including projects, users, repositories, artifacts, policies, and image or chart blobs, can be migrated to Private Registry using a database dump and restore along with a copy of the registry blob storage. This avoids the need to re-push every artifact manually.

This guide covers migrating:

  • The PostgreSQL database of Harbor (all metadata, including projects, users, repositories, artifacts, tags, and policies)

  • The registry blob storage (the actual image layers and Helm chart content)

It does not cover the migration of the configuration of Harbor itself, such as authentication backends, replication endpoints, and quotas. Review your existing Harbor Helm values and reapply the relevant settings to your Private Registry installation separately.

8.2 Prerequisites

  • kubectl access to a separate namespace for each installation. Each namespace must contain only one Harbor or Private Registry release. The commands use component labels and are not safe when multiple releases share a namespace.

  • Enough local or otherwise accessible disk space to hold the database dump and the registry blob archive. Size the blob archive against the actual storage usage of your Harbor registry.

  • The bundled PostgreSQL database for both installations. This procedure does not support external databases.

  • File-system registry storage for both installations. This procedure does not export or restore object-storage content. For object storage, use a provider-specific transfer procedure.

  • A supported source Harbor version. The source Harbor version must match the Harbor version that your Private Registry release is based on. Upgrade an older source to this version before exporting. Do not migrate from a newer source into an older destination.

  • A cluster that supports ephemeral debug containers (kubectl debug, GA since Kubernetes 1.25). This is required to work around a specific Private Registry limitation described in Section 8.7, “Step 4: Restore the data into Private Registry”.

  • If your cluster enforces the restricted Pod Security Standard, plan for a temporary exception: the debug container used in Section 8.7, “Step 4: Restore the data into Private Registry” needs runAsUser: 0 and the SYS_PTRACE capability. Loosening the Pod Security label of the namespace during the migration (or using an equivalent exemption) is the simplest approach.

  • A maintenance window. Both the source Harbor and the destination Private Registry are put into a read-only or quiesced state (and briefly scaled down) while data is copied, so clients cannot push during the migration.

The supported version combination for this procedure is:

Private Registry versionSupported source Harbor versionRequired action for other source versions

1.2.1

2.15.2

Upgrade the source to 2.15.2 before exporting. Do not restore a newer source into an older destination.

Warning
Warning

Take a full backup or storage snapshot of both the source Harbor and destination Private Registry independently of the export this procedure produces. The steps below are destructive to the Private Registry database and registry storage — they overwrite both.

8.3 Migration overview

8.4 Step 1: Export data from Harbor

Set variables for your environment:

SRC_NAMESPACE=<harbor-namespace>
SRC_HOST=<harbor-external-hostname>
SRC_ADMIN_PASSWORD=<harbor-admin-password>
SRC_CA_CERT=<path-to-harbor-ca-certificate>
SRC_HARBOR_VERSION=<harbor-version>
EXPORT_PATH=./harbor-export-$(date +%Y%m%d-%H%M%S)
> install -d -m 0700 "$EXPORT_PATH"

SRC_DB_POD=$(kubectl -n "$SRC_NAMESPACE" get pod -l component=database -o jsonpath='{.items[0].metadata.name}')
SRC_REGISTRY_POD=$(kubectl -n "$SRC_NAMESPACE" get pod -l component=registry -o jsonpath='{.items[0].metadata.name}')
SRC_JOBSERVICE_REPLICAS=$(kubectl -n "$SRC_NAMESPACE" get deploy -l component=jobservice -o jsonpath='{.items[0].spec.replicas}')
SRC_TRIVY_REPLICAS=$(kubectl -n "$SRC_NAMESPACE" get statefulset -l component=trivy -o jsonpath='{.items[0].spec.replicas}')
> [ "$SRC_HARBOR_VERSION" = "2.15.2" ] || { echo "ERROR: upgrade the source Harbor to 2.15.2 before exporting"; exit 1; }

Before starting the export, stop or postpone garbage collection, replication, and vulnerability scans. Read-only mode does not stop these operations, and scaling down their workers can interrupt jobs that are in progress.

Put Harbor into read-only mode so no new writes land during the export:

> curl -s -u "admin:${SRC_ADMIN_PASSWORD}" \
  --cacert "${SRC_CA_CERT}" \
  -H 'Content-Type: application/json' \
  -X PUT "https://${SRC_HOST}/api/v2.0/configurations" \
  -d '{"read_only":true}'

You can also enable this from the Harbor UI, under Administration > Configuration > Repository, instead of using the API call above.

Note
Note

Keep the source in read-only mode until the migration is verified in Section 8.8, “Step 5: Verify the migration”. Only disable it if you need to fall back to the source, as described in Section 8.9, “Rollback”.

Scale down the components that could still write to the database or registry storage in the background (job execution, vulnerability scanning). Leave core, registry, and database running — they’re needed to serve the export itself:

> kubectl -n "$SRC_NAMESPACE" scale deploy -l component=jobservice --replicas=0
> kubectl -n "$SRC_NAMESPACE" scale statefulset -l component=trivy --replicas=0
> kubectl -n "$SRC_NAMESPACE" wait --for=delete pod -l component=jobservice --timeout=120s
> kubectl -n "$SRC_NAMESPACE" wait --for=delete pod -l component=trivy --timeout=120s

Dump the database:

> kubectl -n "$SRC_NAMESPACE" exec "$SRC_DB_POD" -- pg_dump -U postgres -d registry \
  > "${EXPORT_PATH}/harbor-db.sql"

Archive the registry blob storage:

> kubectl -n "$SRC_NAMESPACE" exec "$SRC_REGISTRY_POD" -c registry -- \
  tar czf - -C /storage . > "${EXPORT_PATH}/harbor-registry-blobs.tgz"

Confirm both exports are non-empty before continuing:

> [ -s "${EXPORT_PATH}/harbor-db.sql" ] && [ -s "${EXPORT_PATH}/harbor-registry-blobs.tgz" ] \
  && echo "Export looks good" || echo "ERROR: export incomplete"

Keep $EXPORT_PATH until the migration is verified in Section 8.8, “Step 5: Verify the migration”. Do not delete it after retaining Harbor for rollback.

8.5 Step 2: Retain the source Harbor for rollback

Once you have confirmed that the above export files are present and not empty, keep the source Harbor deployment available until the migrated SUSE Private Registry instance is running correctly with the restored data. If something goes wrong during verification, you can restore the old registry from the export instead of losing access to the source data. You can keep the source deployment scaled down or read-only as long as you are not ready to remove it completely. Just make sure that it is not accepting any writes that the migrated instance will not see.

8.6 Step 3: Install or prepare SUSE Private Registry

Install Private Registry following the standard installation procedure described in Chapter 4, Installation using the command line. Two choices matter specifically for a smooth migration:

External hostname

Configure Private Registry with the same external URL or hostname the source Harbor used. This keeps client endpoints and pull-secret references consistent after the cutover. The fresh installation’s generated secrets are not restored by this procedure, so regenerate robot account credentials after the migration and update clients that use them.

Persistent storage

Provision enough storage for the restored registry blobs plus headroom for future growth, sized against the blob archive produced in Section 8.4, “Step 1: Export data from Harbor”.

Do not seed Private Registry with production traffic yet, because the restore in the next step overwrites its database and registry storage. The fresh installation’s core secret and token certificate authority are also not restored. Regenerate robot account credentials after the restore.

8.7 Step 4: Restore the data into Private Registry

Set variables for the destination:

DST_NAMESPACE=<spr-namespace>
DST_HOST=<spr-external-hostname>
DST_CA_CERT=<path-to-spr-ca-certificate>
DST_DB_POD=$(kubectl -n "$DST_NAMESPACE" get pod -l component=database -o jsonpath='{.items[0].metadata.name}')
DST_REGISTRY_POD=$(kubectl -n "$DST_NAMESPACE" get pod -l component=registry -o jsonpath='{.items[0].metadata.name}')
DST_CORE_REPLICAS=$(kubectl -n "$DST_NAMESPACE" get deploy -l component=core -o jsonpath='{.items[0].spec.replicas}')
DST_JOBSERVICE_REPLICAS=$(kubectl -n "$DST_NAMESPACE" get deploy -l component=jobservice -o jsonpath='{.items[0].spec.replicas}')
DST_TRIVY_REPLICAS=$(kubectl -n "$DST_NAMESPACE" get statefulset -l component=trivy -o jsonpath='{.items[0].spec.replicas}')

Scale down the components that write to or read from the database, but leave registry running. The blob-restore step below needs the registry pod alive to copy files into it:

> kubectl -n "$DST_NAMESPACE" scale deploy -l component=core --replicas=0
> kubectl -n "$DST_NAMESPACE" scale deploy -l component=jobservice --replicas=0
> kubectl -n "$DST_NAMESPACE" scale statefulset -l component=trivy --replicas=0
> kubectl -n "$DST_NAMESPACE" wait --for=delete pod -l component=core --timeout=120s
> kubectl -n "$DST_NAMESPACE" wait --for=delete pod -l component=jobservice --timeout=120s
> kubectl -n "$DST_NAMESPACE" wait --for=delete pod -l component=trivy --timeout=120s

8.7.1 Restore the database

> kubectl -n "$DST_NAMESPACE" cp "${EXPORT_PATH}/harbor-db.sql" "${DST_DB_POD}:/tmp/harbor-db.sql"
> kubectl -n "$DST_NAMESPACE" exec "$DST_DB_POD" -- \
  psql -U postgres -d registry -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;'
> kubectl -n "$DST_NAMESPACE" exec "$DST_DB_POD" -- \
  psql -v ON_ERROR_STOP=1 -U postgres -d registry -f /tmp/harbor-db.sql
> kubectl -n "$DST_NAMESPACE" exec "$DST_DB_POD" -- rm -f /tmp/harbor-db.sql

8.7.2 Restore the registry blobs

Important
Important

The registry container image of Private Registry does not include a tar binary. A plain kubectl cp or kubectl exec …​ tar against it fails, because kubectl cp itself relies on tar being present in the target container for both directions of the copy. (The registry image of upstream Harbor does contain tar, which is why it works fine for the export in Section 8.4, “Step 1: Export data from Harbor”. This limitation is specific to restoring into Private Registry.)

Work around this by attaching an ephemeral debug container — one that does have tar — to the registry pod, sharing its process namespace. From there, the registry container’s filesystem is reachable through /proc/<pid>/root:

> cat > /tmp/debug-profile.json <<'EOF'
{
  "securityContext": {
    "runAsUser": 0,
    "runAsNonRoot": false,
    "capabilities": { "add": ["SYS_PTRACE"] }
  }
}
EOF

> kubectl -n "$DST_NAMESPACE" debug "$DST_REGISTRY_POD" -c restore-helper \
  --image=registry.suse.com/bci/bci-busybox:16.0 \
  --target=registry --custom=/tmp/debug-profile.json -- sleep infinity

Wait for the debug container to be running, then locate the PID of the registry process from inside it:

> kubectl -n "$DST_NAMESPACE" wait --for=jsonpath='{.status.ephemeralContainerStatuses[?(@.name=="restore-helper")].state.running}' \
  pod/"$DST_REGISTRY_POD" --timeout=60s

REGISTRY_PID=$(kubectl -n "$DST_NAMESPACE" exec "$DST_REGISTRY_POD" -c restore-helper -- sh -c '
  for p in /proc/[0-9]*; do
    if tr "\0" " " < "$p/cmdline" 2>/dev/null | grep -q "^registry serve"; then
      basename "$p"; break
    fi
  done
')

Copy the blob archive in and extract it into the registry’s storage path through the debug container:

> kubectl -n "$DST_NAMESPACE" exec "$DST_REGISTRY_POD" -c restore-helper -- \
  sh -c "rm -rf /proc/${REGISTRY_PID}/root/storage/*"
> cat "${EXPORT_PATH}/harbor-registry-blobs.tgz" | \
  kubectl -n "$DST_NAMESPACE" exec -i "$DST_REGISTRY_POD" -c restore-helper -- \
  tar xzf - -C "/proc/${REGISTRY_PID}/root/storage"

Restart the registry deployment so it picks up the restored files with a clean process (this also clears the temporary debug container):

> kubectl -n "$DST_NAMESPACE" rollout restart deploy -l component=registry
> kubectl -n "$DST_NAMESPACE" rollout status deploy -l component=registry --timeout=180s

8.7.3 Bring the rest of Private Registry back up

> kubectl -n "$DST_NAMESPACE" scale deploy -l component=core --replicas="$DST_CORE_REPLICAS"
> kubectl -n "$DST_NAMESPACE" scale deploy -l component=jobservice --replicas="$DST_JOBSERVICE_REPLICAS"
> kubectl -n "$DST_NAMESPACE" scale statefulset -l component=trivy --replicas="$DST_TRIVY_REPLICAS"
> kubectl -n "$DST_NAMESPACE" rollout status deploy -l component=core --timeout=180s
> curl -s --cacert "${DST_CA_CERT}" -u "admin:${SRC_ADMIN_PASSWORD}" \
  -H 'Content-Type: application/json' \
  -X PUT "https://${DST_HOST}/api/v2.0/configurations" \
  -d '{"read_only":false}'

8.8 Step 5: Verify the migration

Check Private Registry’s health endpoint:

> curl -s --cacert "${DST_CA_CERT}" -u "admin:${SRC_ADMIN_PASSWORD}" \
  "https://${DST_HOST}/api/v2.0/health"

Confirm known data survived by querying the API for a project, its repositories/artifacts, and a known user, substituting values you expect to exist:

> curl -s --cacert "${DST_CA_CERT}" -u "admin:${SRC_ADMIN_PASSWORD}" \
  "https://${DST_HOST}/api/v2.0/projects/<project-name>"
> curl -s --cacert "${DST_CA_CERT}" -u "admin:${SRC_ADMIN_PASSWORD}" \
  "https://${DST_HOST}/api/v2.0/projects/<project-name>/repositories/<repo-name>/artifacts/<tag>"
> curl -s --cacert "${DST_CA_CERT}" -u "admin:${SRC_ADMIN_PASSWORD}" \
  "https://${DST_HOST}/api/v2.0/users?username=<username>"

API checks only confirm the database rows migrated. To confirm the blob data itself is intact, do a real pull-back of a known image or Helm chart:

> docker pull <spr-host>/<project-name>/<repo-name>:<tag>
> helm pull oci://<spr-host>/<project-name>/<chart-name> --version <chart-version>
> docker tag <known-local-image> "${DST_HOST}/<project-name>/<repo-name>:migration-test"
> docker push "${DST_HOST}/<project-name>/<repo-name>:migration-test"

A successful pull with the expected digest is the strongest confirmation that the migration succeeded end-to-end.

8.9 Rollback

If verification fails, do not discard the Harbor export. Before directing clients back to the source, disable read-only mode and restore the source worker counts saved in Step 1:

> curl -s -u "admin:${SRC_ADMIN_PASSWORD}" --cacert "${SRC_CA_CERT}" \
  -H 'Content-Type: application/json' \
  -X PUT "https://${SRC_HOST}/api/v2.0/configurations" \
  -d '{"read_only":false}'
> kubectl -n "$SRC_NAMESPACE" scale deploy -l component=jobservice --replicas="$SRC_JOBSERVICE_REPLICAS"
> kubectl -n "$SRC_NAMESPACE" scale statefulset -l component=trivy --replicas="$SRC_TRIVY_REPLICAS"

Resume serving traffic from the source while you investigate. Then retry the Private Registry restore from the same export once the issue is resolved.

9 Backing up and restoring with Velero

Velero provides a framework for backing up and restoring SUSE Private Registry deployments on supported Kubernetes clusters. It captures the Kubernetes API objects of the registry and the data in the persistent volumes that the registry uses. This approach protects the registry configuration and the container images stored in the cluster against accidental deletion or cluster failure.

9.1 Before you start

Velero protects Private Registry by backing up Kubernetes resources and persistent data. In most cases, it is the recommended backup method for registry deployments on Kubernetes.

The scope of the backup depends on whether the registry database and image storage run inside or outside the cluster:

  • Internal PostgreSQL: Velero backs up the registry resources and the data volume for the internal PostgreSQL database, but you must also create a separate database-level backup for application consistency. This is the registry database, not the Kubernetes etcd data store.

  • External PostgreSQL: Velero backs up the registry deployment and its Kubernetes resources. Protect the external PostgreSQL database and the external object storage with the native backup tools of the database or the cloud provider.

Velero does not back up Redis data. Redis mainly holds sessions and caches, so users must log in again after a restore.

Important
Important

For a full disaster-recovery workflow, the Velero backup repository must be separate from the cluster where Private Registry runs. A cluster-local backend such as local-path, a PVC-backed store, or an NFS-backed store is not a portable production backup target. Velero stores backups in an object store, and it does not provide a PVC-backed or NFS-backed BackupStorageLocation that supports a full-cluster recovery workflow.

9.2 Prerequisites

Before starting the backup process, make sure you have the following:

  • A supported Kubernetes cluster and a Private Registry release that is compatible with both the cluster and the Velero version you use.

  • Helm, kubectl and the Velero CLI installed.

  • A Velero server with the storage-provider plugin that your object store requires. See the supported providers.

  • An off-cluster BackupStorageLocation for production use.

  • For File System Backup, the Velero node agent running in the cluster. Install it with velero install --use-node-agent, or enable it in the Velero Helm chart.

  • For CSI volume snapshots, a CSI driver that supports snapshots and a VolumeSnapshotClass that is labeled velero.io/csi-volumesnapshot-class: "true".

  • A recovery cluster that provides the same StorageClass names as the source cluster, so that the restored PVCs can bind.

9.3 Recommended storage configuration

For production environments and full disaster recovery, use a storage backend that is independent from the primary cluster. This keeps backups available even if the cluster is lost or the registry namespace is deleted.

Supported backup backends include:

  • Amazon S3

  • Azure Blob Storage

  • Google Cloud Storage

  • Any other approved Velero provider plugin

For the complete supported-provider list, see the Velero supported providers page.

9.4 What to expect after a restore

A successful backup does not preserve every runtime state. After a restore, expect the following:

  • Lost memory data: The registry keeps repository and artifact pull times in memory and writes them to the database periodically. Any data that was not yet written to the database is lost. This typically has a low impact on operations.

  • Lost user sessions: The Redis volume is excluded from the backup, so all active sessions are lost and users must log in again.

  • Hanging tasks: Replication, garbage collection and security scans can remain in a pending or interrupted state and must be restarted or stopped manually from the administrator interface.

  • Crash consistency: Velero volume snapshots are crash-consistent. This protects the storage state after a crash, but it does not guarantee that the database transactions are complete. For a production deployment with an internal PostgreSQL database, also create an application-consistent database dump.

9.5 Backing up Private Registry

9.5.1 Step 1: Preparing the registry

Before you create a backup, record the information that you need for a later restore. Then stop the tasks that could change registry data while the backup runs. Replace <PRIVATE_REGISTRY_NAMESPACE> with the namespace of your deployment and <RELEASE_NAME> with the name of your Helm release.

  1. Save your Private Registry chart version, application version and Helm values in a secure recovery record:

    > helm list -n <PRIVATE_REGISTRY_NAMESPACE>
    > helm get values <RELEASE_NAME> -n <PRIVATE_REGISTRY_NAMESPACE> \
      -o yaml > <VALUES_FILE>.yaml
    Note
    Note

    The -o yaml option is required. Without it, helm get values prints a USER-SUPPLIED VALUES: header, and the resulting file is not a valid values file.

  2. Record your namespace, release name, persistent volume claim (PVC) names, and storage classes. The following command only lists the PVCs in the namespace; it does not back up the PVC data or the storage configuration:

    > kubectl get pvc -n <PRIVATE_REGISTRY_NAMESPACE>
  3. Record your TLS, OIDC, image-pull, and database secret references. The following command only lists the existing secrets in the namespace; it does not back up the secrets or their values:

    > kubectl get secrets -n <PRIVATE_REGISTRY_NAMESPACE>
  4. Enable Repository Read Only in the Private Registry administrator interface.

  5. Stop or postpone image pushes, chart pushes, garbage collection, replication, and security scans.

9.5.2 Step 2: Excluding Redis

Redis is not part of the backup target because it mainly contains user session and cache data. Exclude it from the backup so that the restored environment does not contain stale session state.

  1. Identify the Redis resources of your release:

    > kubectl get sts,pod -n <PRIVATE_REGISTRY_NAMESPACE> -l component=redis

    The Redis PVC is created from a volume claim template and does not carry the component label, so it cannot be selected by label. Its name is always data-<RELEASE_NAME>-harbor-redis-0.

  2. Exclude the Redis pod, PVC, and persistent volume (PV) from the backup:

    > kubectl label pod -n <PRIVATE_REGISTRY_NAMESPACE> -l component=redis \
      velero.io/exclude-from-backup=true --overwrite
    > kubectl label pvc -n <PRIVATE_REGISTRY_NAMESPACE> \
      data-<RELEASE_NAME>-harbor-redis-0 velero.io/exclude-from-backup=true --overwrite
    > kubectl label pv $(kubectl get pvc -n <PRIVATE_REGISTRY_NAMESPACE> \
      data-<RELEASE_NAME>-harbor-redis-0 -o jsonpath='{.spec.volumeName}') \
      velero.io/exclude-from-backup=true --overwrite
Warning
Warning

Do not add the velero.io/exclude-from-backup label to the Redis StatefulSet. Excluding the StatefulSet removes the Redis workload from the backup entirely. After a restore, the cluster then has no Redis deployment, and the core and job service pods fail repeatedly. When only the pod, the PVC, and the PV are excluded, the StatefulSet is restored and creates an empty Redis volume.

9.5.3 Step 3: Creating the backup

Use the backup method that matches your storage platform. Replace <BACKUP_NAME> with a name for the backup and <RETENTION_DURATION> with the retention period, for example, 720h.

File System Backup

File System Backup copies the volume content through the Velero node agent. It does not depend on volume snapshot support in the storage provider, so it also works with storage backends such as local-path.

> velero backup create <BACKUP_NAME> \
  --include-namespaces <PRIVATE_REGISTRY_NAMESPACE> \
  --default-volumes-to-fs-backup \
  --ttl <RETENTION_DURATION> \
  --wait

CSI volume snapshots

If your storage platform supports CSI snapshots, use the snapshot-based approach instead. It requires a CSI driver with snapshot support and a VolumeSnapshotClass that is labeled velero.io/csi-volumesnapshot-class: "true".

> velero backup create <BACKUP_NAME> \
  --include-namespaces <PRIVATE_REGISTRY_NAMESPACE> \
  --snapshot-volumes \
  --ttl <RETENTION_DURATION> \
  --wait

9.5.4 Step 4: Verifying the backup

A backup is not complete until you verify that the expected resources and volumes are included.

  1. Review the result and the volume coverage:

    > velero backup describe <BACKUP_NAME> --details
  2. Confirm that the phase is Completed, and that the registry PVC, the PostgreSQL PVC, and the job service PVC are included in the backup.

  3. Confirm that no Redis volume is listed in the backup:

    > kubectl -n velero get podvolumebackups \
      -l velero.io/backup-name=<BACKUP_NAME> \
      -o custom-columns='POD:.spec.pod.name,VOLUME:.spec.volume,PHASE:.status.phase'
  4. Disable Repository Read Only after the backup completes successfully.

9.6 Automating backups

You can automate Private Registry backups with Velero schedules. A schedule creates periodic backups based on a standard cron expression, which provides consistent recovery points without manual intervention.

  1. Verify that the Redis pod, PVC, and PV are still labeled with velero.io/exclude-from-backup=true:

    > kubectl get pod,pvc -n <PRIVATE_REGISTRY_NAMESPACE> \
      -l velero.io/exclude-from-backup=true
    Note
    Note

    The label on a pod is lost when the pod is recreated, for example, after an upgrade or a node restart. Check the Redis pod label again after any operation that recreates the pod. The labels on the PVC and the PV persist.

  2. Create a schedule that uses the same backup method as your manual backup. For example, to run a File System Backup every day at 2:00 AM:

    > velero schedule create <SCHEDULE_NAME> \
      --schedule="0 2 * * *" \
      --include-namespaces <PRIVATE_REGISTRY_NAMESPACE> \
      --default-volumes-to-fs-backup \
      --ttl <RETENTION_DURATION>

    If you back up with CSI snapshots, replace --default-volumes-to-fs-backup with --snapshot-volumes.

  3. Verify that the schedule is active and configured correctly:

    > velero schedule get <SCHEDULE_NAME>

9.7 Restoring Private Registry

9.7.1 Restoring a deployment with an internal PostgreSQL database

Use this procedure when PostgreSQL runs inside the Kubernetes cluster. Velero recreates the namespace, the workloads, the PVCs, the secrets, and the Helm release secret. Therefore, do not install the chart before you restore.

Warning
Warning

Do not run helm install before the restore. Velero skips every resource that already exists. A pre-created release therefore keeps the empty PVCs and the new secrets of the fresh installation, and the backed-up data is not restored. A new installation also regenerates the <RELEASE_NAME>-core secret and the token CA, which invalidates all existing robot accounts.

  1. Provision the target Kubernetes cluster with the same StorageClass names as the source cluster, and install the required Ingress and Velero components. Configure Velero with the same BackupStorageLocation that holds the backup.

  2. Wait until the backup is visible in the target cluster before you restore it. If the backup is still synchronizing from the object store, velero backup get can report that it is unavailable or return not found until the object-store upload completes:

    > velero backup get <BACKUP_NAME>
  3. Restore the backup:

    > velero restore create <RESTORE_NAME> \
      --from-backup <BACKUP_NAME> \
      --wait
  4. Review the result and confirm that all items were restored:

    > velero restore describe <RESTORE_NAME> --details

    A warning about the kube-root-ca.crt ConfigMap that already exists is expected and can be ignored. Warnings about PVCs, secrets, or workloads that already exist indicate that the namespace was not empty, and that the restore is incomplete.

  5. Confirm that the Helm release is known again in the restored cluster:

    > helm list -n <PRIVATE_REGISTRY_NAMESPACE>
  6. Verify that the TLS, OIDC, image-pull, and database secrets were restored. Recreate only the secrets that are managed outside the namespace, such as certificates issued by an external certificate manager. Do not recreate the secrets that the chart generates, because this invalidates the robot accounts of the registry.

  7. If the recovery cluster is reached at a different address than the source cluster, update externalURL and the Ingress host in the saved values file. Then upgrade the release with that file:

    > helm upgrade <RELEASE_NAME> \
      oci://registry.suse.com/private-registry/<APP_VERSION>/private-registry-helm \
      --namespace <PRIVATE_REGISTRY_NAMESPACE> \
      --version <CHART_VERSION> \
      -f <VALUES_FILE>.yaml
  8. Disable Repository Read Only.

Note
Note

The chart version, the application version, and the values file that you saved in Section 9.5.1, “Step 1: Preparing the registry” are a fallback for rebuilding the release manually. They are not required for the restore itself.

9.7.2 Restoring a deployment with an external PostgreSQL database

Use this procedure when your database and object storage are managed outside of the Kubernetes cluster.

  1. Provision the target Kubernetes cluster and the required Velero components, and configure Velero with the same BackupStorageLocation that holds the backup.

  2. Confirm that the backup is available in the target cluster before you start the restore:

    > velero backup get <BACKUP_NAME>
  3. Restore the external PostgreSQL database with the native recovery procedure of your database provider.

  4. Restore the external registry object-storage data with its native recovery procedure.

  5. Restore the Private Registry Kubernetes resources and the in-cluster PVCs into an empty namespace:

    > velero restore create <RESTORE_NAME> \
      --from-backup <BACKUP_NAME> \
      --wait
  6. Verify that the connection secret of the external database and the TLS CA or OIDC credentials were restored, and recreate only the ones that are managed outside the namespace.

  7. Start the registry and verify that it connects to the restored database.

  8. Disable Repository Read Only.

9.8 Verification and troubleshooting

After the restore, validate that the registry is fully operational.

  1. Check the PVCs and the pods, and confirm that the PVCs are bound and the pods are running:

    > kubectl get pvc -n <PRIVATE_REGISTRY_NAMESPACE>
    > kubectl get pods -n <PRIVATE_REGISTRY_NAMESPACE>
  2. Validate the API health, the administrator login, and the image pull and image push operations.

  3. Confirm that the projects, users, and artifacts that existed at backup time are present again, and that a robot account can still authenticate.

If the restore does not complete as expected, check the following:

  • velero restore describe <RESTORE_NAME> --details reports resources that already exist. The target namespace was not empty. Restore into a clean cluster or a clean namespace.

  • PVCs remain Pending. The StorageClass of the source cluster does not exist in the recovery cluster, or it cannot provision the requested volume.

  • No volume data is restored. The Velero node agent was not running when the backup was created, so no PodVolumeBackup was produced. Verify the backup with velero backup describe <BACKUP_NAME> --details.

  • Velero cannot find the backup in the recovery cluster. The BackupStorageLocation is not the one that holds the backup, or it is not in the Available phase. Check it with kubectl -n velero get backupstoragelocation.

  • Users cannot log in, and robot accounts fail to authenticate. The chart-generated secrets were recreated instead of restored.

10 Frequently asked questions

10.1 Product overview and differentiators

What kind of subscription do customers need for SUSE Private Registry?

It is included in Rancher Suite and offered as an add-on for Rancher Prime.

The pricing of the add-on is the same as other add-ons.

What are the differentiators with Harbor (from Application Collection or upstream)?

Use SUSE Private Registry if you need:

  • Level 3 (L3) support for product issues.

  • A predictable release cycle.

  • Patched images for known vulnerabilities. Upstream Harbor images from Docker Hub often contain numerous unpatched vulnerabilities.

    Over time, SUSE will also add out-of-the-box integrations with Rancher and prioritize feature requests from customers.

Do customers have to buy the same number of add-on SUSE Private Registry subscriptions as Rancher Prime subscriptions?

Yes, just like other add-ons such as SUSE Security. An additional advantage of the SUSE Private Registry subscription model is that it allows customers to run as many deployments as they need.

10.2 Relationship with upstream Harbor

How does the release cycle align with upstream Harbor?

The release cycle of SUSE Private Registry is independent of the release cycle of upstream Harbor.

When releasing new versions of SUSE Private Registry, SUSE aims to include the latest version of the upstream Harbor project that meets SUSE quality assurance and maintenance requirements.

Will you publish migration considerations for customers running Harbor from other sources?

Yes. Migration instructions are available in Chapter 8, Migrating data from upstream Harbor to SUSE Private Registry.

10.3 Deployment and installation

Do you support installing SUSE Private Registry via docker-compose?

No. You must install and configure SUSE Private Registry using its Helm chart. This chart is also used for ongoing management (Day 2 operations) and can be integrated with GitOps workflows.

Do you recommend deployments on the local (Rancher Manager) cluster?

No. SUSE recommends deploying SUSE Private Registry on a downstream cluster. This makes the registry accessible to other downstream clusters that need to consume images.

What are the deployment best practices for a high-availability (HA) environment?

For HA, do not deploy SUSE Private Registry on the Rancher Management cluster to avoid resource contention.

You can deploy SUSE Private Registry on a dedicated cluster, or on one or more of your application clusters.

For instructions, see Chapter 6, High Availability setup. Note that you must provide your own HA components for the Postgres database, Valkey or Redis server, and Ingress controller. These components are not deployed by the SUSE Private Registry Helm chart and are not supported by SUSE.

Is the SUSE Private Registry Helm chart going to be added to the Application Collection eventually?

Yes, SUSE plans to publish the chart there in the future.

Do you ship a Kubernetes operator?

SUSE Private Registry does not ship with a dedicated operator. It is installed and managed using its Helm chart, which can be integrated with GitOps. SUSE continues to evaluate operator-based management for future releases.

10.4 Security, scanning and signing

Do you integrate any signing tool within SUSE Private Registry?

SUSE Private Registry does not sign images itself, but it can store, distribute and verify Open Container Initiative (OCI)-compatible signatures.

A widely used tool is cosign, which can sign images and store the signatures in SUSE Private Registry.

cosign signatures attached to images are viewable and downloadable from the SUSE Private Registry portal.

Is it possible to sign images with Notary or Cosign for approval during the deployment process?

SUSE Private Registry can store, distribute and verify cosign signatures.

SUSE Private Registry itself does not sign images. You must sign images during their build process, and then upload the signature to the registry along with the image.

Notary is not included or supported with SUSE Private Registry.

Are images scanned for Common Vulnerabilities and Exposures (CVEs) only with Trivy, or can it use SUSE Security (NeuVector) as well?

Images are scanned with Trivy, which comes out of the box. SUSE Security can be added in addition to or as a replacement for Trivy.

Is it possible to run ClamAV or similar malware scans?

By default, SUSE Private Registry scans images using Trivy. You can also configure SUSE Security (NeuVector) as a scanner.

In the pull-through cache scenario, will the vulnerability criteria be enforced?

Yes, but with a known limitation. Due to upstream Harbor issues, vulnerability criteria are not enforced on the first pull of an image because the scan has not yet completed.

For complete coverage, pair SUSE Private Registry with SUSE Security admission controls.

Are the images of SUSE Private Registry hardened?

Yes. The container images for SUSE Private Registry are hardened. They are based on SUSE Linux Enterprise Base Container Images (SLE BCI) and built in the same enterprise-grade SUSE Build Service used for SUSE Linux Enterprise. This ensures a secure supply chain. The images are also signed, and SUSE publishes their Supply-chain Levels for Software Artifacts (SLSA) attestations.

Are the images of SUSE Private Registry signed? How?

The images are signed with cosign. You can verify them by saving the PEM-formatted signing key published at KB 000021411.

Then run, for example:

cosign verify --key container-key.pem registry.suse.com/private-registry/harbor-portal:latest

10.5 Replication and sync

Is there a plan for a plug-in to sync with SUSE Registry and SUSE Application Collection?

Yes, such a feature is planned.

Does SUSE Private Registry offer multi-site replication?

Yes. SUSE Private Registry supports multi-site, policy-based image replication (pull and push). You can synchronize images across multiple SUSE Private Registry deployments while keeping each registry independent.

10.6 Features and integration with Rancher

Is there a plan to integrate the Registry within Rancher using an Extension?

Yes. SUSE plans deeper integration with Rancher Prime and other offerings. Future enhancements being considered include single sign-on (SSO) integration, simplified setup for SUSE Security scanners and Application Collection mirroring, monitoring with SUSE Observability, and a Rancher UI extension.

Will SUSE add the Registry to the Training Catalog?

This is not yet planned. If you are interested in training material, contact SUSE to discuss possibilities with the Training team.

10.7 Support and documentation

Is there a support policy for SUSE Private Registry?

Yes. The support policy of SUSE Private Registry is the same as any other Rancher Prime add-on.

If the SUSE Private Registry documentation lacks information, can I refer to the official Harbor documentation?

Yes. Since SUSE Private Registry is based on Harbor, the official Harbor documentation is a useful resource. For features specific to the SUSE version, refer to the SUSE Private Registry documentation.

The Release notes specify which upstream Harbor version corresponds to your SUSE Private Registry version.

11 Troubleshooting

This section provides solutions to problems that you may encounter when deploying or using SUSE Private Registry.

I am encountering the 401 unauthorized error when trying to install SUSE Private Registry

The full version of the error message looks as follows:

Error: INSTALLATION FAILED:
GET "https://registry.suse.com/v2/private-registry/private-registry-helm/tags/list":
response status code 401: unauthorized:
authentication required:
[map[Action:pull Class: Name:private-registry/private-registry-helm Type:repository]]

To install and use SUSE Private Registry, you need the following:

  • A qualifying subscription that includes this product, such as a Rancher Suite subscription or a SUSE Private Registry Add-on subscription. If you do not have a qualifying subscription, contact your SUSE representative.

  • Log in to SUSE Registry with Helm using the SCC mirroring credentials of the SCC organization which holds the subscription. Refer to Section 4.1, “Obtaining Kubernetes secrets from the SUSE Customer Center” for more details.

A Overriding the SUSE Private Registry Helm chart

The SUSE Private Registry (Private Registry) Helm chart is delivered with default values. For guidance about using replicas, scheduling, exposure, and persistence values in a resilient deployment, see Chapter 6, High Availability setup. You can adjust the Helm chart installation in one of the following ways:

Ensure you replace <APP_VERSION> with your specific application version (for example, 1.2).

  • Append specific parameters to the --set flags on the helm install command line, for example:

    $ helm install <RELEASE_NAME> \
    oci://registry.suse.com/private-registry/<APP_VERSION>/private-registry-helm \
    --namespace <PRIVATE_REGISTRY_NAMESPACE> \
    --set harborAdminPassword=<MY_PASSWORD> \
    --set externalURL=https://<PRIVATE_REGISTRY_FQDN> \
    --set expose.ingress.hosts.core=<PRIVATE_REGISTRY_FQDN>
  • Create a SUSE custom suse_registry_override.yaml file and pass it to the -f flag, for example:

      $ helm install <RELEASE_NAME> \
      oci://registry.suse.com/private-registry/<APP_VERSION>/private-registry-helm \
      --namespace <PRIVATE_REGISTRY_NAMESPACE>
      -f suse_registry_override.yaml

A1 Examples of SUSE Registry Helm override files

Example A1: Minimal deployment with Ingress
expose:
  type: ingress 1
  ingress:
    hosts:
      core: <PRIVATE_REGISTRY_FQDN> 2

externalURL: https://<PRIVATE_REGISTRY_FQDN> 3

harborAdminPassword: "<MY_PASSWORD>" 4

database:
  internal:
    password: "<MY_PASSWORD_POSTGRESQL>"

redis:
  internal:
   password: "<MY_PASSWORD_REDIS>"

1

How SUSE Registry is exposed. Can be ingress, loadBalancer, nodePort or clusterIP. Default is ingress.

2

Host name for the Kubernetes internal networking configuration.

3

URL where the SUSE Registry application runs. It is used to generate links in the user interface, redirects and also for API responses.

4

The administrator password to the application.

Example A2: Typical deployment with loadBalancer
expose:
  type: loadBalancer 1
  tls:
    enabled: true
    certSource: secret 2
    secret:
      secretName: <SECRET_NAME>

    auto:
      commonName: <PRIVATE_REGISTRY_FQDN> 3

externalURL: https://<PRIVATE_REGISTRY_FQDN> 4

harborAdminPassword: "<MY_PASSWORD>" 5

database:
  internal:
    password: "<MY_PASSWORD_POSTGRESQL>"

redis:
  internal:
   password: "<MY_PASSWORD_REDIS>"

1

How SUSE Registry is exposed. Can be ingress, loadBalancer, nodePort or clusterIP. Default is ingress.

2

Can be auto, secret or none. Depending on the option, you may have to include additional values.

3

When using TLS encryption, this field must match the externalURL value.

4

URL where the SUSE Registry application runs. It is used to generate links in the user interface, redirects and also for API responses.

5

The administrator password to the application.

A2 Overriding Helm chart parameters and values

The following tables list all parameters with descriptions that you can use to override the default installation values.

Global parameters
global.imageRegistry

Sets a global override for the container image registry used for all images.

global.imagePullSecrets

Sets global pull secrets for accessing the container image registry.

Common parameters
harborAdminPassword

Sets the initial password for Harbor administrator. Change it from portal after deployment. Default is Harbor12345.

externalURL

Specifies the external URL for harbor-core service. Default is https://core.harbor.domain.

existingSecretAdminPasswordKey

Sets the key name in the secret containing Harbor administrator password. Default is HARBOR_ADMIN_PASSWORD.

imagePullSecrets

Sets the imagePullSecrets names for all deployments.

updateStrategy.type

Sets the update strategy for deployments with persistent volumes. Accepts RollingUpdate or Recreate. Use Recreate when RWM for volumes is not supported. Default is RollingUpdate.

logLevel

Sets the log level for Harbor services. Accepts fatal, error, warn, info, debug or trace. Default is debug.

enableMigratehelmHook

Runs database migration job via Helm hook. When true, separates migration job from harbor-core. Default is false.

caSecretName

Specifies the secret name containing the ca.crt key.

Proxy parameters
proxy.httpProxy

Specifies the HTTP proxy server URL. Default is "".

proxy.httpsProxy

Specifies the HTTPS proxy server URL. Default is "".

proxy.noProxy

Sets URLs that bypass the proxy configuration. Default is 127.0.0.1,localhost,.local,.internal.

proxy.components

Sets components that use the proxy configuration. Default is ["core","jobservice","trivy"].

Expose parameters
expose.type

Specifies service exposure type: ingress, clusterIP, nodePort or loadBalancer. Default is ingress.

expose.tls.enabled

Enables TLS. Default is true.

expose.tls.certSource

Sets TLS certificate source as auto, secret or none. Default is auto.

expose.tls.auto.commonName

Sets certificate common name when type is not ingress.

expose.tls.secret.secretName

Specifies name of secret containing tls.crt (certificate) and tls.key (private key).

expose.ingress.hosts.core

Sets Harbor core service host in Ingress rule. Default is core.harbor.domain.

expose.ingress.controller

Sets Ingress controller type. Supports default, gce, alb, f5-bigip and ncp. Default is default.

expose.ingress.kubeVersionOverride

Overrides Kubernetes version for Ingress templating.

expose.ingress.annotations

Sets Ingress annotations.

expose.ingress.labels

Sets Ingress-specific labels. Default is {}.

expose.clusterIP.name

Sets ClusterIP service name. Default is harbor.

expose.clusterIP.annotations

Sets ClusterIP service annotations. Default is {}.

expose.clusterIP.ports.httpPort

Sets HTTP service port. Default is 80.

expose.clusterIP.ports.httpsPort

Sets HTTPS service port. Default is 443.

expose.clusterIP.labels

Sets ClusterIP-specific labels. Default is {}.

expose.nodePort.name

Sets NodePort service name. Default is harbor.

expose.nodePort.ports.http.port

Sets HTTP service port. Default is 80.

expose.nodePort.ports.http.nodePort

Sets HTTP node port. Default is 30002.

expose.nodePort.ports.https.port

Sets HTTPS service port. Default is 443.

expose.nodePort.ports.https.nodePort

Sets HTTPS node port. Default is 30003.

expose.nodePort.annotations

Sets NodePort annotations.

expose.nodePort.labels

Sets NodePort-specific labels. Default is {}.

expose.loadBalancer.name

Sets service name. Default is harbor.

expose.loadBalancer.IP

Sets loadBalancer IP when IP assignment is supported. Default is "".

expose.loadBalancer.ports.httpPort

Sets HTTP service port. Default is 80.

expose.loadBalancer.ports.httpsPort

Sets HTTPS service port. Default is 30002.

expose.loadBalancer.annotations

Sets loadBalancer service annotations. Default is {}.

expose.loadBalancer.labels

Sets loadBalancer-specific labels. Default is {}.

expose.loadBalancer.sourceRanges

Specifies IP address ranges for loadBalancerSourceRanges. Default is [].

Persistence parameters
persistence.enabled

Enables or disables data persistence. Default is true.

persistence.resourcePolicy

keep prevents removal of PVCs during a Helm delete operation. Empty value deletes PVCs after chart deletion. Default is keep.

persistence.persistentVolumeClaim.registry.existingClaim

The existing PVC that must be created manually before binding. Requires a subPath specification if the PVC is shared with other components.

persistence.persistentVolumeClaim.registry.storageClass

The storageClass that provisions the volume.

persistence.persistentVolumeClaim.registry.subPath

The subpath in the volume.

persistence.persistentVolumeClaim.registry.accessMode

The access mode of the volume. Default is ReadWriteOnce.

persistence.persistentVolumeClaim.registry.size

The size of the volume. Default is 5Gi.

persistence.persistentVolumeClaim.registry.annotations

The annotations of the volume.

persistence.persistentVolumeClaim.jobservice.jobLog.existingClaim

The existing PVC that must be created manually before binding. Requires a subPath specification if the PVC is shared with other components.

persistence.persistentVolumeClaim.jobservice.jobLog.storageClass

The storageClass that provisions the volume.

persistence.persistentVolumeClaim.jobservice.jobLog.subPath

The subpath in the volume.

persistence.persistentVolumeClaim.jobservice.jobLog.accessMode

The access mode of the volume. Default is ReadWriteOnce.

persistence.persistentVolumeClaim.jobservice.jobLog.size

The size of the volume. Default is 1Gi.

persistence.persistentVolumeClaim.jobservice.jobLog.annotations

The annotations of the volume.

persistence.persistentVolumeClaim.database.existingClaim

The existing PVC that must be created manually before binding. Requires a subPath specification if the PVC is shared with other components.

persistence.persistentVolumeClaim.database.storageClass

The storageClass that provisions the volume.

persistence.persistentVolumeClaim.database.subPath

The subpath in the volume. Ignored when an external database is used.

persistence.persistentVolumeClaim.database.accessMode

The access mode of the volume. Ignored when an external database is used. Default is ReadWriteOnce.

persistence.persistentVolumeClaim.database.size

The size of the volume. Ignored when an external database is used. Default is 1Gi.

persistence.persistentVolumeClaim.database.annotations

The annotations of the volume.

persistence.persistentVolumeClaim.redis.existingClaim

The existing PVC that must be created manually before binding. Requires a subPath specification if the PVC is shared with other components.

persistence.persistentVolumeClaim.redis.storageClass

The storageClass that provisions the volume. Uses default StorageClass if not specified.

persistence.persistentVolumeClaim.redis.subPath

The subpath in the volume. Ignored when an external Valkey is used.

persistence.persistentVolumeClaim.redis.accessMode

The access mode of the volume. Ignored when an external Valkey is used. Default is ReadWriteOnce.

persistence.persistentVolumeClaim.redis.size

The size of the volume. Ignored when an external Valkey is used. Default is 1Gi.

persistence.persistentVolumeClaim.redis.annotations

The annotations of the volume.

persistence.persistentVolumeClaim.trivy.existingClaim

The existing PVC that must be created manually before binding. Requires a subPath specification if the PVC is shared with other components.

persistence.persistentVolumeClaim.trivy.storageClass

The storageClass that provisions the volume. Uses default StorageClass if not specified.

persistence.persistentVolumeClaim.trivy.subPath

The subpath in the volume.

persistence.persistentVolumeClaim.trivy.accessMode

The access mode of the volume. Default is ReadWriteOnce.

persistence.persistentVolumeClaim.trivy.size

The size of the volume. Default is 1Gi.

persistence.persistentVolumeClaim.trivy.annotations

The annotations of the volume.

persistence.imageChartStorage.disableredirect

Controls redirect management from content back-ends. Set to true to disable redirects for unsupported back-ends. Default is false.

persistence.imageChartStorage.caBundleSecretName

The name of secret containing CA bundle for self-signed storage service certificates.

persistence.imageChartStorage.type

The storage type for images and charts: filesystem, azure, gcs, s3, swift, or oss. Default is filesystem.

persistence.imageChartStorage.gcs.existingSecret

The name of existing secret containing the GCS service account JSON key. The key must be gcs-key.json. Default is "".

persistence.imageChartStorage.gcs.useWorkloadIdentity

Enables workload identity usage in a GKE cluster. Default is false.

nginx parameters
nginx.image.repository

The image repository for nginx. Default is private-registry/harbor-nginx.

nginx.image.tag

The image tag for nginx.

nginx.replicas

The number of replicas to run. Default is 1.

nginx.revisionHistoryLimit

The maximum number of old ReplicaSet revisions to retain. Default is 10.

nginx.resources

The compute resources allocated for the container. Default is undefined.

nginx.automountServiceAccountToken

Controls automatic mounting of the service account token. Default is false.

nginx.nodeSelector

The node labels used for pod assignment. Default is {}.

nginx.tolerations

The pod assignment tolerations. Default is [].

nginx.affinity

The node or pod affinity rules. Default is {}.

nginx.topologySpreadConstraints

The rules for spreading pods across failure-domains such as regions or availability zones. Default is [].

nginx.podAnnotations

The annotations added to the nginx pod. Default is {}.

Portal parameters
portal.image.repository

Repository location for the portal image. Default is private-registry/harbor-portal.

portal.image.tag

Tag for the portal image. Default is 3.11.

portal.replicas

Number of replicas to create. Default is 1.

portal.revisionHistoryLimit

Maximum number of old ReplicaSet revisions to retain. Default is 10.

portal.resources

Resources allocated to the container. Default is undefined.

portal.automountServiceAccountToken

Controls automatic mounting of the service account token. Default is false.

portal.nodeSelector

Node labels used for pod assignment. Default is {}.

portal.tolerations

Tolerations used for pod assignment. Default is [].

portal.affinity

Node and pod affinity settings. Default is {}.

portal.topologySpreadConstraints

Defines pod distribution across failure-domains such as regions or availability zones. Default is [].

portal.podAnnotations

Annotations added to the portal pod. Default is {}.

portal.serviceAnnotations

Annotations added to the portal service. Default is {}.

portal.priorityClassName

Priority class name for pod execution.

portal.initContainers

Init containers to be run before the controller container starts. Default is [].

Core parameters
core.image.repository

The repository for the Harbor core image. Default is private-registry/harbor-core.

core.image.tag

The tag for the Harbor core image. Default is 2.11.

core.replicas

The number of replicas. Default is 1.

core.revisionHistoryLimit

The revision history limit. Default is 10.

core.startupProbe.initialDelaySeconds

The initial delay in seconds for the startup probe. Default is 10.

core.resources

The resources to allocate for the container. Default is undefined.

core.automountServiceAccountToken

Mounts the service account token. Default is false.

core.nodeSelector

The node labels for pod assignment. Default is {}.

core.tolerations

The tolerations for pod assignment. Default is [].

core.affinity

The node or pod affinities. Default is {}.

core.topologySpreadConstraints

The constraints that define how pods are spread across failure-domains like regions or availability zones. Default is [].

core.podAnnotations

The annotations to add to the core pod. Default is {}.

core.serviceAnnotations

The annotations to add to the core service. Default is {}.

core.configureUserSettings

A JSON string in the environment variable CONFIG_OVERWRITE_JSON to configure user settings.

core.quotaUpdateProvider

The provider for updating project quota usage, options are redis or db. Default is db.

core.secret

Used when core server communicates with other components.

core.secretName

The name of a Kubernetes secret to use your own TLS certificate and private key for token encryption or decryption.

core.tokenKey

The PEM-formatted RSA private key used to sign service tokens.

core.tokenCert

The PEM-formatted certificate signed by core.tokenKey used to validate service tokens.

core.xsrfKey

The XSRF key, automatically generated if not specified.

core.priorityClassName

The priority class to run the pod as.

core.artifactPullAsyncFlushDuration

The time duration for asynchronously updating artifact pull time and repository pull count.

core.gdpr.deleteUser

Enables GDPR compliant user deletion. Default is false.

core.gdpr.auditLogsCompliant

Enables GDPR compliance for audit logs by changing username to its CRC32 value if that user was deleted from the system. Default is false.

core.initContainers

The init containers to run before the controller’s container starts. Default is [].

Jobservice parameters
jobservice.image.repository

The repository for the jobservice image. Default is private-registry/harbor-jobservice.

jobservice.image.tag

The tag for the jobservice image. Default is 2.11.

jobservice.replicas

The number of replicas. Default is 1.

jobservice.revisionHistoryLimit

The revision history limit. Default is 10.

jobservice.maxJobWorkers

The maximum number of job workers. Default is 10.

jobservice.jobLoggers

The loggers for jobs: file, database or stdout. Default is [file].

jobservice.loggerSweeperDuration

The duration in days to keep job logs (ignored if jobLoggers is set to stdout). Default is 14.

jobservice.notification.webhook_job_max_retry

The maximum number of retries for webhook notification sending. Default is 3.

jobservice.notification.webhook_job_http_client_timeout

The HTTP client timeout in seconds for webhook notification sending. Default is 3.

jobservice.reaper.max_update_hours

The maximum time in hours to wait for a task to finish. If the task is not finished after the specified hours, it is marked as an error but continues to run. Default is 24.

jobservice.reaper.max_dangling_hours

The maximum time in hours for execution in running state without a new task created. Default is 168.

jobservice.resources

The [resources] to allocate for container. Default is undefined.

jobservice.automountServiceAccountToken

Mounts the service account token. Default is false.

jobservice.nodeSelector

The node labels for pod assignment. Default is {}.

jobservice.tolerations

The tolerations for pod assignment. Default is [].

jobservice.affinity

The node or pod affinities. Default is {}.

jobservice.topologySpreadConstraints

The constraints that define how pods are spread across failure-domains like regions or availability zones. Default is [].

jobservice.podAnnotations

The annotations to add to the jobservice pod. Default is {}.

jobservice.priorityClassName

The priority class to run the pod as.

jobservice.secret

The secret used when job service communicates with other components. If a secret key is not specified, Helm generates it. Must be a string of 16 characters.

jobservice.initContainers

The init containers to run before the controller’s container starts. Default is [].

Registry parameters
registry.registry.image.repository

The repository location for the registry image. Default is private-registry/harbor-registry.

registry.registry.image.tag

The tag for the registry image. Default is 2.11.

registry.registry.resources

The [resources] to allocate for container. Default is undefined.

registry.controller.image.repository

The repository location for the registry controller image. Default is private-registry/harbor-registryctl.

registry.controller.image.tag

The tag for the registry controller image. Default is 2.11.

registry.controller.resources

The [resources] to allocate for container. Default is undefined.

registry.replicas

The number of replica instances. Default is 1.

registry.revisionHistoryLimit

The maximum number of revisions to maintain in history. Default is 10.

registry.nodeSelector

The node labels for pod assignment. Default is {}.

registry.automountServiceAccountToken

Controls whether to mount the service account token. Default is false.

registry.tolerations

The tolerations for pod assignment. Default is [].

registry.affinity

The node or pod affinities. Default is {}.

registry.topologySpreadConstraints

The constraints that define pod distribution across failure-domains such as regions or availability zones. Default is [].

registry.middleware

Middleware support for a CDN between back-end storage and Docker pull recipient.

registry.podAnnotations

The annotations to add to the registry pod. Default is {}.

registry.priorityClassName

The priority class for pod execution.

registry.secret

The secret that secures the upload state between client and registry storage back-end.

registry.credentials.username

The username for Harbor core’s internal registry access. Default is harbor_registry_user.

registry.credentials.password

The password for Harbor core’s internal registry access. Default is harbor_registry_password.

registry.credentials.existingSecret

An existing secret containing the password for registry instance access in htpasswd auth mode. Default is "".

registry.credentials.htpasswdString

The login and password in htpasswd string format. Excludes registry.credentials.username and registry.credentials.password. Default is undefined.

registry.relativeurls

Returns relative URLs in Location headers when true. Required if Harbor is behind a reverse proxy. Default is false.

registry.upload_purging.enabled

Enables purging of upload directories. Default is true.

registry.upload_purging.age

The time period after which files in upload directories are removed, default is one week. Default is 168h.

registry.upload_purging.interval

The time interval between purge operations. Default is 24h.

registry.upload_purging.dryrun

Enables dryrun mode for upload purging. Default is false.

registry.initContainers

The init containers that run before the controller’s container starts. Default is [].

Trivy parameters
trivy.enabled

Enables or disables the Trivy scanner. Default is true.

trivy.image.repository

The repository for the Trivy adapter image. Default is private-registry/harbor-trivy-adapter.

trivy.image.tag

The tag for the Trivy adapter image. Default is 2.11.

trivy.resources

The resources to allocate for the Trivy adapter container. Default is undefined.

trivy.automountServiceAccountToken

Whether to mount the service account token. Default is false.

trivy.replicas

The number of Pod replicas. Default is 1.

trivy.debugMode

Enables Trivy debug mode for troubleshooting. Default is false.

trivy.vulnType

Comma-separated list of vulnerability types (os and library). Default is os,library.

trivy.severity

Comma-separated list of vulnerability severities to check. Default is UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL.

trivy.ignoreUnfixed

Displays only fixed vulnerabilities. Default is false.

trivy.insecure

Skips registry certificate verification. Default is false.

trivy.skipUpdate

Disables Trivy database downloads from GitHub. Default is false.

trivy.skipJavaDBUpdate

Requires manual download of the trivy-java.db file when enabled. Default is false.

trivy.offlineScan

Prevents Trivy from sending API requests to identify dependencies. Default is false.

trivy.securityCheck

Comma-separated list of security issues to detect. Default is vuln.

trivy.timeout

The duration to wait for scan completion. Default is 5m0s.

trivy.gitHubToken

The GitHub access token required for database downloads. Default is undefined.

trivy.priorityClassName

The priority class for running the pod. Default is undefined.

trivy.topologySpreadConstraints

Defines pod distribution constraints across failure domains. Default is undefined.

trivy.initContainers

List of init containers to run before the main container starts. Default is [].

Database parameters
database.type

The database type. Set to external when using an external database. Default is internal.

database.internal.image.repository

The repository for the database image. Default is private-registry/harbor-db.

database.internal.image.tag

The tag for the database image. Default is 2.11.

database.internal.password

The password for the internal database. Default is changeit.

database.internal.shmSizeLimit

The shared memory size limit for PostgreSQL (typically 50% of the container memory limit). Default is 512Mi.

database.internal.resources

The resources allocated for the database container. Default is undefined.

database.internal.automountServiceAccountToken

Controls whether the service account token is mounted. Default is false.

database.internal.initContainer.migrator.resources

The resources allocated for the database migrator init container. Default is undefined.

database.internal.initContainer.permissions.resources

The resources allocated for the database permissions init container. Default is undefined.

database.internal.nodeSelector

The node labels for pod assignment. Default is {}.

database.internal.tolerations

The tolerations for pod assignment. Default is [].

database.internal.affinity

The node or pod affinity settings. Default is {}.

database.internal.priorityClassName

The priority class for running the pod. Default is undefined.

database.internal.livenessProbe.timeoutSeconds

The timeout in seconds for the liveness probe (range: 1-5s). Default is 1.

database.internal.readinessProbe.timeoutSeconds

The timeout in seconds for the readiness probe (range: 1-5s). Default is 1.

database.internal.extrInitContainers

Additional init containers that run before the database container starts. Default is [].

database.external.host

The host name of the external database. Default is 192.168.0.1.

database.external.port

The port number of the external database. Default is 5432.

database.external.username

The username for the external database. Default is user.

database.external.password

The password for the external database. Default is password.

database.external.coreDatabase

The database name used by the core service. Default is registry.

database.external.existingSecret

The existing secret containing the database password. The key must be password. Default is "".

database.external.sslmode

The connection method for the external database. Options: require, verify-full, verify-ca, disable. Default is disable.

database.maxIdleConns

The maximum number of idle connections in the pool (0 or less means no idle connections are retained). Default is 50.

database.maxOpenConns

The maximum number of open connections to the database (0 or less means unlimited). Default is 100.

database.podAnnotations

The annotations to add to the database pod. Default is {}.

Valkey / Redis parameters
redis.type

The Redis deployment type. Set to external for external Redis. Default is internal.

redis.internal.image.repository

The repository for the Redis image. Default is private-registry/harbor-redis.

redis.internal.image.tag

The tag for the Redis image. Default is 7.2.

redis.internal.resources

The resources allocated for the Redis container. Default is undefined.

redis.internal.automountServiceAccountToken

Controls whether the service account token is mounted. Default is false.

redis.internal.nodeSelector

The node labels for pod assignment. Default is {}.

redis.internal.tolerations

The tolerations for pod assignment. Default is [].

redis.internal.affinity

The node or pod affinity settings. Default is {}.

redis.internal.priorityClassName

The priority class for running the Redis pod. Default is undefined.

redis.internal.jobserviceDatabaseIndex

The database index for jobservice. Default is 1.

redis.internal.registryDatabaseIndex

The database index for registry. Default is 2.

redis.internal.trivyAdapterIndex

The database index for Trivy adapter. Default is 5.

redis.internal.harborDatabaseIndex

The database index for miscellaneous Harbor business logic. Default is 0.

redis.internal.cacheLayerDatabaseIndex

The database index for Harbor’s cache layer. Default is 0.

redis.internal.initContainers

The init containers that run before the Redis container starts. Default is [].

redis.external.addr

The address of the external Redis instance. Default is 192.168.0.2:6379.

redis.external.sentinelMasterSet

The name of the Redis Sentinel master set (if applicable). Default is undefined.

redis.external.coreDatabaseIndex

The database index for core. Default is 0.

redis.external.jobserviceDatabaseIndex

The database index for jobservice. Default is 1.

redis.external.registryDatabaseIndex

The database index for registry. Default is 2.

redis.external.trivyAdapterIndex

The database index for Trivy adapter. Default is 5.

redis.external.harborDatabaseIndex

The database index for miscellaneous Harbor business logic. Default is 0.

redis.external.cacheLayerDatabaseIndex

The database index for Harbor’s cache layer. Default is 0.

redis.external.username

The username for external Redis authentication. Default is undefined.

redis.external.password

The password for external Redis authentication. Default is undefined.

redis.external.existingSecret

The existing secret containing the Redis password. The key must be REDIS_PASSWORD. Default is "".

redis.podAnnotations

The annotations to add to the Redis pod. Default is {}.

Exporter parameters
exporter.replicas

The number of replicas to run. Default is 1.

exporter.revisionHistoryLimit

The revision history limit. Default is 10.

exporter.podAnnotations

Annotations to add to the exporter pod. Default is {}.

exporter.image.repository

The repository for the exporter image. Default is private-registry/harbor-exporter.

exporter.image.tag

The tag for the exporter image. Default is 2.11.

exporter.nodeSelector

Node labels for pod assignment. Default is {}.

exporter.tolerations

Tolerations for pod assignment. Default is [].

exporter.affinity

Node or Pod affinities. Default is {}.

exporter.topologySpreadConstraints

Constraints that define how Pods spread across failure-domains like regions or availability zones. Default is [].

exporter.automountServiceAccountToken

Controls whether to mount the serviceAccountToken. Default is false.

exporter.cacheDuration

The cache duration for information collected by the exporter. Default is 30.

exporter.cacheCleanInterval

The cache clean interval for information collected by the exporter. Default is 14400.

exporter.priorityClassName

The priority class to run the pod as. Default is undefined.

Metrics parameters
metrics.enabled

Enables Harbor metrics. Default is false.

metrics.core.path

The URL path for core metrics. Default is /metrics.

metrics.core.port

The port for core metrics. Default is 8001.

metrics.registry.path

The URL path for registry metrics. Default is /metrics.

metrics.registry.port

The port for registry metrics. Default is 8001.

metrics.exporter.path

The URL path for exporter metrics. Default is /metrics.

metrics.exporter.port

The port for exporter metrics. Default is 8001.

metrics.serviceMonitor.enabled

Enables creation of a Prometheus ServiceMonitor (requirePrometheusus CRDs). Default is false.

metrics.serviceMonitor.additionalLabels

Additional labels to apply to the ServiceMonitor manifest. Default is "".

metrics.serviceMonitor.interval

The scrape interval for Harbor metrics. Default is "".

metrics.serviceMonitor.metricRelabelings

The relabeling rules for metrics before ingestion. Default is [].

metrics.serviceMonitor.relabelings

The relabeling rules for metrics before scraping. Default is [].

Trace parameters
trace.enabled

Enables tracing functionality. Default is false.

trace.provider

The tracing provider (jaeger or otel). Jaeger version should be 1.26+. Default is jaeger.

trace.sample_rate

The sampling rate for trace data. 1 samples 100%, 0.5 samples 50%. Default is 1.

trace.namespace

The namespace to differentiate different Harbor services.

trace.attributes

A key-value dictionary for user-defined attributes in trace provider initialization.

trace.jaeger.endpoint

The endpoint for Jaeger tracing. Default is http://hostname:14268/api/traces.

trace.jaeger.username

The username for Jaeger authentication.

trace.jaeger.password

The password for Jaeger authentication.

trace.jaeger.agent_host

The agent host for Jaeger.

trace.jaeger.agent_port

The agent port for Jaeger. Default is 6831.

trace.otel.endpoint

The endpoint for OpenTelemetry tracing. Default is hostname:4318.

trace.otel.url_path

The URL path for OpenTelemetry. Default is /v1/traces.

trace.otel.compression

Enables compression for OpenTelemetry. Default is false.

trace.otel.insecure

Establishes an insecure connection for OpenTelemetry. Default is true.

trace.otel.timeout

The timeout in seconds for OpenTelemetry. Default is 10.

Cache parameters
cache.enabled

Enables the cache layer. Default is false.

cache.expireHours

The expiration time in hours for the cache layer. Default is 24.

B Example of a Private Registry HA setup Helm chart

The following example values file illustrates a starting point for a Private Registry HA setup. It uses external PostgreSQL and external Valkey or Redis services. This example uses three replicas for the main services. Two replicas provide basic redundancy, but three replicas leave two instances available after one pod or node becomes unavailable. Three replicas also provide more capacity during planned maintenance or a rolling update.

Replicas alone do not make the deployment tolerate a node failure. The example therefore also sets topologySpreadConstraints, so that the replicas of a component are placed on different nodes, and nodeSelector, so that the registry components run on a dedicated worker-node pool with sufficient capacity. The selected nodes must have the label specified in the nodeSelector configuration.

For the design decisions behind these values, see Chapter 6, High Availability setup. For the complete list of values, see Appendix A, Overriding the SUSE Private Registry Helm chart.

expose:
  type: ingress
  tls:
    enabled: true
    certSource: secret 1
    secret:
      secretName: <TLS_SECRET_NAME>
  ingress:
    hosts:
      core: <PRIVATE_REGISTRY_FQDN> 2

externalURL: https://<PRIVATE_REGISTRY_FQDN> 3

harborAdminPassword: "<MY_PASSWORD>" 4

core:
  replicas: 3 5
  nodeSelector:
    <NODE_LABEL_KEY>: <NODE_LABEL_VALUE> 6
  topologySpreadConstraints: 7
    - maxSkew: 1
      topologyKey: kubernetes.io/hostname
      whenUnsatisfiable: DoNotSchedule
      matchLabelKeys:
        - pod-template-hash 8
      labelSelector:
        matchLabels:
          component: core
          release: <RELEASE_NAME>

portal:
  replicas: 3
  # Repeat the nodeSelector and topologySpreadConstraints of the core
  # component, with "component: portal" in the label selector. 9

registry:
  replicas: 3
  # Repeat with "component: registry" in the label selector.

jobservice:
  replicas: 3
  jobLoggers:
    - database 10
  # Repeat with "component: jobservice" in the label selector.

# Increase Trivy replicas when vulnerability scanning is a critical workload
trivy:
  replicas: 3
  # Repeat the nodeSelector and topologySpreadConstraints of the core
  # component, with "component: trivy" in the label selector, but without
  # matchLabelKeys.

# With "expose.type: ingress" the chart does not deploy its own nginx proxy,
# so "nginx.replicas" has no effect. Endpoint redundancy comes from the
# Ingress controller. 11

metrics:
  enabled: true 12
  serviceMonitor:
    enabled: true 13

# While not strictly for the HA of the registry itself, consider increasing exporter replicas for robust monitoring availability
exporter:
  replicas: 3
  # Repeat with "component: exporter" in the label selector.

# The CA certificate that signed the certificate of the external PostgreSQL
# server. The secret must contain the key "ca.crt".
caBundleSecretName: <CA_SECRET_NAME> 14

database:
  type: external
  external: 15
    host: <POSTGRESQL_HOST>
    port: "5432"
    username: <POSTGRESQL_USER>
    coreDatabase: "registry"
    existingSecret: <POSTGRESQL_SECRET_NAME> 16
    sslmode: "verify-full" 17

redis:
  type: external
  external: 18
    addr: <VALKEY_HOST>:6379 19
    sentinelMasterSet: "" 20
    coreDatabaseIndex: "0" 21
    jobserviceDatabaseIndex: "1"
    registryDatabaseIndex: "2"
    trivyAdapterIndex: "5"
    harborDatabaseIndex: "6" 22
    cacheLayerDatabaseIndex: "7" 23
    username: "" 24
    existingSecret: <VALKEY_SECRET_NAME> 25

persistence:
  enabled: true 26
  resourcePolicy: keep 27
  persistentVolumeClaim:
    registry:
      storageClass: <STORAGE_CLASS_NAME>
      accessMode: ReadWriteMany 28
      size: 500Gi 29
    trivy:
      storageClass: <STORAGE_CLASS_NAME>
      size: 20Gi 30

1

Can be auto, secret or none. Depending on the option, you may have to include additional values.

2

Core service host name in Ingress rule.

3

The external URL for the harbor-core service. The host name must match expose.ingress.hosts.core and the certificate in <TLS_SECRET_NAME>.

4

The initial administrator password. In production, store the password in a Kubernetes secret and reference it with existingSecretAdminPasswordKey instead of setting it in the values file.

5

Number of replicas to create. Two replicas provide basic redundancy. Three replicas are a recommended production starting point because two instances remain available after one pod or node becomes unavailable. Replicas do not guarantee node failure tolerance unless you distribute them across different nodes or failure domains.

6

Schedules the component on a dedicated worker-node pool. The selected nodes must carry this label.

7

Places the replicas of the component on different nodes. Set topologyKey to topology.kubernetes.io/zone to spread the replicas across availability zones instead.
Kubernetes enforces the constraint when it schedules a pod, and does not rebalance the replicas later.
Verify the pod labels of your release with kubectl get pods --namespace <PRIVATE_REGISTRY_NAMESPACE> --show-labels and adjust matchLabels to match them.

8

Counts only the pods of the revision being rolled out. Without it, a rolling update also counts the pods it is about to replace, so the replacements can be placed on fewer nodes than maxSkew allows, and they are not rebalanced afterwards.

9

Apply the same nodeSelector and topologySpreadConstraints values to every component that runs more than one replica, including the exporter and Trivy. Without them, Kubernetes may place all replicas of a component on the same node, or on nodes outside the selected worker-node pool.
Also repeat matchLabelKeys for portal, registry, jobservice and exporter, which the chart deploys as Deployment resources. Omit it for trivy: the chart deploys trivy as a StatefulSet, whose pods do not have a pod-template-hash label, and whose default rolling update replaces one replica at a time without running old and new replicas side by side.

10

Writes job logs to the database instead of a volume. The default file logger uses the persistence.persistentVolumeClaim.jobservice.jobLog volume, which defaults to ReadWriteOnce. A ReadWriteOnce volume confines all job service replicas to a single node. Keep the file logger only if the job log volume uses ReadWriteMany.

11

The chart deploys its own nginx proxy only when expose.type is clusterIP, nodePort or loadBalancer. With ingress, raise the replica count of the Ingress controller instead.

12

Required for the exporter to be deployed. Defaults to false.

13

Creates a Prometheus ServiceMonitor. Requires the Prometheus Operator CRDs in the cluster. Use metrics.serviceMonitor.additionalLabels when your monitoring stack requires a label to discover the ServiceMonitor.

14

The name of an existing secret containing the CA certificate that signed the certificate of the external PostgreSQL server. The key in the secret must be ca.crt. Required when sslmode is verify-ca or verify-full.
The same CA bundle is injected into the trust store of the core, job service, registry and Trivy components.

15

Provide the database connection details in the external section.

16

The name of an existing secret containing the database password. The key in the secret must be password. Alternatively, set password directly in the external section.

17

Accepts one of the following values:

disable

Do not use SSL. Not recommended for production.

require

Always use SSL and skip verification.

verify-ca

Always use SSL. Verify that the certificate presented by the server was signed by a trusted CA.

verify-full

Always use SSL. Verify that the certificate presented by the server was signed by a trusted CA and the server host name matches the one in the certificate.
verify-ca and verify-full also require caBundleSecretName. Without it, the components cannot verify the certificate of the database and fail to start.

18

Provide the connection information in the external section.

19

Supports direct and Sentinel connections. Cluster mode is not supported.
Address for a direct connection is <VALKEY_HOST>:<VALKEY_PORT>.
Address for a Sentinel connection is <SENTINEL1_HOST>:<SENTINEL1_PORT>,<SENTINEL2_HOST>:<SENTINEL2_PORT>…​

20

The name of the set of Valkey or Redis instances to monitor. It must be set for a Sentinel connection.

21

Must be 0, because the client library used by Private Registry does not support another value for the core database index.

22

Optional. The database index for miscellaneous business logic. Defaults to 0 but can be configured to 6.

23

Optional. The database index for the cache layer. Defaults to 0 but can be configured to 7. The index is only used when cache.enabled is true, which is not the default.

24

If empty, it is authenticated against the default user.

25

The name of an existing secret containing the Valkey or Redis password. The key in the secret must be REDIS_PASSWORD. Alternatively, set password directly in the external section.

26

To store all the images, metadata and scans, ensure that the persistence-related settings (Persistence parameters) are properly configured.

27

Keeps the PVCs when the Helm release is deleted. This setting does not replace backups and does not protect the PVCs from deletion outside Helm.

28

Replace <STORAGE_CLASS_NAME> with a storage class that supports the selected access mode and remains available after a node failure. Use ReadWriteMany only when the storage implementation supports simultaneous access from the nodes that run the registry pods.

29

The chart default of 5Gi is installation-oriented. Size the volume from the recommendations in Section 2.2, “Hardware and sizing recommendations”.

30

The chart default of 5Gi may not be enough for the Trivy vulnerability database and cache. Each Trivy replica gets its own volume.

For a production deployment, store the registry content in external object storage instead of a file system volume. Object storage removes the shared-volume requirement and keeps the registry content available when a worker node fails. Replace the persistence.persistentVolumeClaim.registry values with an imageChartStorage configuration. For example:

persistence:
  enabled: true
  resourcePolicy: keep
  imageChartStorage:
    type: s3 1
    s3:
      region: <S3_REGION>
      bucket: <S3_BUCKET>
      accesskey: <S3_ACCESS_KEY>
      secretkey: <S3_SECRET_KEY>
    disableredirect: true 2
  persistentVolumeClaim:
    trivy:
      storageClass: <STORAGE_CLASS_NAME>
      size: 20Gi

1

Accepts filesystem, azure, gcs, s3, swift or oss. The keys below type are specific to the selected back end; see the imageChartStorage section of the chart’s values.yaml for the full set. Protect the object store with its own availability, access-control, retention, and backup policies.

2

By default, the registry responds to pull requests by redirecting clients directly to object storage. Set disableredirect to true when the object storage is only reachable from inside the cluster, so that the registry serves the content itself.

C GNU Free Documentation License

Copyright © 2000, 2001, 2002 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

C1 0. PREAMBLE

The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or non-commercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.

This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.

We have designed this License to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.

C2 1. APPLICABILITY AND DEFINITIONS

This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.

A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.

A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document’s overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.

The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.

The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.

A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque".

Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.

The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work’s title, preceding the beginning of the body of the text.

A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition.

The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.

C3 2. VERBATIM COPYING

You may copy and distribute the Document in any medium, either commercially or non-commercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.

You may also lend copies, under the same conditions stated above, and you may publicly display copies.

C4 3. COPYING IN QUANTITY

If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document’s license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.

If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.

If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.

It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.

C5 4. MODIFICATIONS

You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:

  1. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission.

  2. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement.

  3. State on the Title page the name of the publisher of the Modified Version, as the publisher.

  4. Preserve all the copyright notices of the Document.

  5. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices.

  6. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below.

  7. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document’s license notice.

  8. Include an unaltered copy of this License.

  9. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence.

  10. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission.

  11. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein.

  12. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles.

  13. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version.

  14. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section.

  15. Preserve any Warranty Disclaimers.

If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version’s license notice. These titles must be distinct from any other section titles.

You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties—​for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.

You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.

The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.

C6 5. COMBINING DOCUMENTS

You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.

The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.

In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements".

C7 6. COLLECTIONS OF DOCUMENTS

You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.

You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.

C8 7. AGGREGATION WITH INDEPENDENT WORKS

A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation’s users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.

If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document’s Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.

C9 8. TRANSLATION

Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.

If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.

C10 9. TERMINATION

You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

C11 1. FUTURE REVISIONS OF THIS LICENSE

The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See https://www.gnu.org/copyleft/.

Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation.

C12 ADDENDUM: How to use this License for your documents

  Copyright (c) YEAR YOUR NAME.
  Permission is granted to copy, distribute and/or modify this document
  under the terms of the GNU Free Documentation License, Version 1.2
  or any later version published by the Free Software Foundation;
  with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
  A copy of the license is included in the section entitled "GNU
  Free Documentation License".

If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with…​Texts."" line with this:

  with the Invariant Sections being LIST THEIR TITLES, with the
  Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.

If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.

If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.