Install Harbor for Service Registry

This topic explains how to deploy Harbor into a workload cluster in Tanzu Kubernetes Grid.

Harbor is an open-source, trusted, cloud-native container registry that stores, signs, and scans content. Tanzu Kubernetes Grid includes signed, packaged binaries for Harbor which you can deploy into a workload cluster to provide container registry services for that cluster. This Harbor package extends the open-source Docker distribution by adding functionalities usually required by users such as security and identity control and management.


Notary and Chartmuseum are deprecated in Harbor v2.6 and are scheduled to be removed in a future release as noted in the Harbor v2.6.0 release notes. Users should switch to Sigstore Cosign for container signing and verification.

Harbor Registry and ExternalDNS

VMware recommends installing ExternalDNS alongside the Harbor registry on infrastructures with load balancing (for example, vSphere with NSX Advanced Load Balancer), especially in production or other environments in which Harbor availability is important.

If the IP address to the ingress load balancer changes, ExternalDNS automatically picks up the change and re-maps the new address to the Harbor hostname. This precludes the need to manually re-map the address as described in Connect to the Harbor User Interface.


Prepare a Cluster for Harbor Deployment

To prepare a cluster for Harbor deployment:

  1. Set the context of kubectl to the workload cluster. For example:

    kubectl config use-context tkg-services-admin@tkg-services
  2. If the cluster does not already have the standard package installed, install it:


    If you are targeting a plan-based cluster (legacy), skip this step. For plan-based clusters, the tanzu-standard package repository is automatically enabled in every cluster, in the tanzu-package-repo-global namespace.

    tanzu package repository add tanzu-standard --url PACKAGE-REPOSITORY-ENDPOINT --namespace tkg-system

    Where PACKAGE-REPOSITORY-ENDPOINT is the URL of the standard package repository. For this release, the URL is

    See List Package Repositories to obtain this value from the Tanzu CLI, or in Tanzu Mission Control see the Addons > Repositories list in the Cluster pane.

  3. If you have not already done so, install the cert-manager and Contour packages. For instructions, see Install Contour for Ingress Control.

  4. (Optional) Install the ExternalDNS package. For instructions, see Install ExternalDNS for Service Discovery.

  5. Proceed to Deploy Harbor into a Cluster below.

Deploy Harbor into a Cluster

Follow this procedure to deploy Harbor into a workload cluster:

  1. Confirm that the Harbor package is available in the cluster:

    tanzu package available list -A
  2. Retrieve the version of the available package:

    tanzu package available list -A
  3. Download the Harbor package from the standard package repository:

    imgpkg pull -b -o /tmp/harbor-package-PACKAGE-VERSION

    Where PACKAGE-VERSION is the version of package as listed by tanzu package available list but with a _ character substituted for the + character. It must also include the prefix v. For example, `v2.6.3_vmware.1-tkg.1.

    For example:

    imgpkg pull -b -o /tmp/harbor-package-v2.6.3_vmware.1-tkg.1
    1. Set the mandatory passwords and secrets in the harbor-data-values.yaml file by doing one of the following:

      • To automatically generate random passwords and secrets, run:

        image_url=$(kubectl -n tkg-system get packages -o jsonpath='{.spec.template.spec.fetch[0].imgpkgBundle.image}')
        imgpkg pull -b $image_url -o /tmp/harbor-package-PACKAGE-VERSION
        cp /tmp/harbor-package-PACKAGE-VERSION/config/values.yaml harbor-data-values.yaml
        bash /tmp/harbor-package-PACKAGE-VERSION/config/scripts/ harbor-data-values.yaml

        Where PACKAGE-VERSION is the version of the Harbor package that you want to install.

        For example, for the Harbor package v2.6.3, run:

        image_url=$(kubectl -n tkg-system get packages -o jsonpath='{.spec.template.spec.fetch[0].imgpkgBundle.image}')
        imgpkg pull -b $image_url -o /tmp/harbor-package-2.6.3
        bash /tmp/harbor-package-2.6.3/config/scripts/ harbor-data-values.yaml
      • To set your own passwords and secrets, update the following entries in the harbor-data-values.yaml file:

        • harborAdminPassword
        • secretKey
        • database.password
        • core.secret
        • core.xsrfKey
        • jobservice.secret
        • registry.secret
    2. Specify other settings in the harbor-data-values.yaml file.

      • Set the hostname setting to the hostname you want to use to access Harbor. For example,
      • To use your own certificates, update the tls.crt, tls.key, and ca.crt settings with the contents of your certificate, key, and CA certificate. The certificate can be signed by a trusted authority or be self-signed. If you leave these blank, Tanzu Kubernetes Grid automatically generates a self-signed certificate.
      • If you used the script, optionally update the harborAdminPassword with something that is easier to remember.
      • Non-empty values are required for the following:

        • storageClass: Under persistence.persistentVolumeClaim, for registry, jobservice, database, redis, and trivy, set storageClass to a storage profile returned by kubectl get sc.

        With the azure-file storage class you cannot change filesystem permissions after the disk is mounted, because of an Azure issue described in “Could not change permissions” error while using Azure Files in the Azure documentation.

        • pspNames: Set pspNames to PSP values returned by kubectl get psp, for example, "vmware-system-restricted,vmware-system-privileged".
      • Optionally, update other persistence settings to specify how Harbor stores data.

        If you need to store a large quantity of container images in Harbor, set persistence.persistentVolumeClaim.registry.size to a larger number.

      To see more information about the values in the harbor-data-values.yaml file, run the below command against your target cluster:

      tanzu package available get --values-schema

      Where AVAILABLE-VERSION is the version of the Harbor package. The --values-schema flag retrieves the valuesSchema section from the Package API resource for the Harbor package. You can set the output format, --output, for the values schema to yaml, json, or table.

      For example:

      tanzu package available get --values-schema
    3. Remove all comments in the harbor-data-values.yaml file:

      yq -i eval '... comments=""' harbor-data-values.yaml
  4. Install the package:

    tanzu package install harbor \
    --package \
    --values-file harbor-data-values.yaml \
    --namespace TARGET-NAMESPACE


    • TARGET-NAMESPACE is the namespace in which you want to install the Harbor package, Harbor package app, and any other Kubernetes resources that describe the package. For example, the my-packages or tanzu-cli-managed-packages namespace. If the --namespace flag is not specified, the Tanzu CLI installs the package and its resources in the default namespace. The Harbor pods and any other resources associated with the Harbor component are created in the tanzu-system-registry namespace; do not install the Harbor package into this namespace.
    • AVAILABLE-PACKAGE-VERSION is the version that you retrieved above.

    For example:

    tanzu package install harbor \
    --package \
    --version 2.6.3+vmware.1-tkg.1 \
    --values-file harbor-data-values.yaml \
    --namespace my-packages
  5. If you are using as storageclass, patch the Harbor Scandata Volume EmptyDir Overlay as follows, to make the scandata volume as an empty directory which would not affect the normal scandata export functionality, meanwhile to avoid the AZ conflict when both jobLog volume and scandata volume need to be mounted into a same jobservice pod. Or you could simply change storage class VolumeBoundMode from Immediate to WaitForFirstConsumer. Be aware of the second method is a cluster level change behaviour.

    1. Create a file scandata-empty-dir-overlay.yaml containing the Harbor Scandata Volume EmptyDir Overlay code below.

    2. Create a generic secret with the overlay:

      kubectl -n test  create secret generic scandata-empty-dir-overlay -o yaml --dry-run=client --from-file=scandata-emptyDir-overlay.yaml | kubectl apply -f -
    3. Patch the Harbor package with the secret:
      kubectl -n test annotate packageinstalls harbor
    4. Delete the existing Harbor pods so that they re-create:
      kubectl delete pods --all -n my-packages
  6. Confirm that the harbor package has been installed:

    tanzu package installed list -A

    To see more details about the package, you can also run:

    tanzu package installed get harbor --namespace PACKAGE-NAMESPACE

    Where PACKAGE-NAMESPACE is the namespace in which the harbor package is installed.

  7. Confirm that the harbor app has been successfully reconciled in your PACKAGE-NAMESPACE:

    kubectl get apps -A

    If the status is not Reconcile Succeeded, view the full status details of the harbor app. Viewing the full status can help you troubleshoot the problem.

    kubectl get app harbor --namespace PACKAGE-NAMESPACE -o yaml

    Where PACKAGE-NAMESPACE is the namespace in which you installed the package. If troubleshooting does not help you solve the problem, you must uninstall the package before installing it again:

    tanzu package installed delete harbor --namespace PACKAGE-NAMESPACE
  8. Confirm that the Harbor services are running by listing all of the pods in the cluster:

    kubectl get pods -A

    In the tanzu-system-regisry namespace, you should see the harbor core, database, jobservice, notary, portal, redis, registry, and trivy services running in a pod with names similar to the following:

    NAMESPACE               NAME                                    READY   STATUS    RESTARTS   AGE
    tanzu-system-ingress    contour-6b568c9b88-h5s2r                1/1     Running   0          26m
    tanzu-system-ingress    contour-6b568c9b88-mlg2r                1/1     Running   0          26m
    tanzu-system-ingress    envoy-wfqdp                             2/2     Running   0          26m
    tanzu-system-registry   harbor-core-557b58b65c-4kzhn            1/1     Running   0          23m
    tanzu-system-registry   harbor-database-0                       1/1     Running   0          23m
    tanzu-system-registry   harbor-jobservice-847b5c8756-t6kfs      1/1     Running   0          23m
    tanzu-system-registry   harbor-notary-server-6b74b8dd56-d7swb   1/1     Running   2          23m
    tanzu-system-registry   harbor-notary-signer-69d4669884-dglzm   1/1     Running   2          23m
    tanzu-system-registry   harbor-portal-8f677757c-t4cbj           1/1     Running   0          23m
    tanzu-system-registry   harbor-redis-0                          1/1     Running   0          23m
    tanzu-system-registry   harbor-registry-85b96c7777-wsdnj        2/2     Running   0          23m
    tanzu-system-registry   harbor-trivy-0                          1/1     Running   0          23m
  9. Obtain the Harbor CA certificate from the harbor-tls secret in the tanzu-system-registry namespace:

    kubectl -n tanzu-system-registry get secret harbor-tls -o=jsonpath="{\.crt}" | base64 -d

    Make a copy of the output.

Connect to the Harbor User Interface

The Harbor UI is exposed via the Envoy service load balancer that is running in the tanzu-system-ingress namespace in the cluster. To allow users to connect to the Harbor UI, you must map the address of the Envoy service load balancer to the hostname of the Harbor service, for example,

  1. Obtain the address of the Envoy service load balancer.

    kubectl get svc envoy -n tanzu-system-ingress -o jsonpath='{.status.loadBalancer.ingress[0]}'

    On vSphere without NSX Advanced Load Balancer (ALB), the Envoy service is exposed via NodePort instead of LoadBalancer, so the above output will be empty, and you can use the IP address of any worker node in the cluster instead. On vSphere with NSX ALB, the Envoy service has a Load Balancer IP address similar to

  2. Map the address of the Envoy service load balancer to the hostname of the Harbor service. For clusters that are running on vSphere, you must add an IP to hostname mapping in /etc/hosts or add corresponding A records in your DNS server. For example, if the IP address is, add the following to /etc/hosts:

    On Windows machines, the equivalent to /etc/hosts/ is C:\Windows\System32\Drivers\etc\hosts.

Users can now connect to the Harbor UI by navigating to in a Web browser and log in as user admin with the harborAdminPassword that you configured in harbor-data-values.yaml.

Push and Pull Images to and from Harbor

Now that Harbor is set up, you can push images to it to make them available for your cluster to pull.

  1. If Harbor uses a self-signed certificate, download the Harbor CA certificate from and install it on your local machine, so Docker can trust this CA certificate.

    • On Linux, save the certificate as /etc/docker/certs.d/
    • On macOS, follow this procedure.
    • On Windows, right-click the certificate file and select Install Certificate.
  2. Log in to the Harbor registry with the user admin. When prompted, enter the harborAdminPassword that you set when you installed the Harbor package in the cluster.

    docker login -u admin
  3. Tag an existing image that you have already pulled locally, for example, nginx:1.7.9.

    docker tag nginx:1.7.9
  4. Push the image to the Harbor registry.

    docker push
  5. Now you can pull the image from the Harbor registry on any machine where the Harbor CA certificate is installed.

    docker pull

Update a Running Harbor Deployment

If you need to make changes to the configuration of the Harbor package after deployment, follow these steps to update your deployed Harbor package.

  1. Update the Harbor configuration in harbor-data-values.yaml. For example, you can increase the amount of registry storage by updating the persistence.persistentVolumeClaim.registry.size value.

  2. Update the configuration of the installed package:

    tanzu package installed update harbor \
    --values-file harbor-data-values.yaml \


    • INSTALLED-PACKAGE-VERSION is the version of the installed Harbor package.
    • INSTALLED-PACKAGE-NAMESPACE is the namespace in which the Harbor package is installed.

    For example:

    tanzu package installed update harbor \
    --version 2.6.3+vmware.1-tkg.1 \
    --values-file harbor-data-values.yaml \
    --namespace my-packages

The Harbor package will be reconciled using the new value or values that you added. It can take up to five minutes for kapp-controller to apply the changes.

For more information about the tanzu package installed update command, see Update a Package in Install and Manage Packages. You can use this command to update the version and the configuration of an installed package.

Harbor Scandata Volume EmptyDir Overlay


#@ load("@ytt:overlay", "overlay")
#@overlay/match by=overlay.and_op(overlay.subset({"kind": "Deployment"}), overlay.subset({"metadata": {"name": "harbor-jobservice"}}))
        #@overlay/match by="name"
        - name: job-scandata-exports
        - name: job-scandata-exports
            sizeLimit: 500Mi
check-circle-line exclamation-circle-line close-line
Scroll to top icon