Harbor

Published: 4 Nov. 2026 Last updated: 4 Nov. 2026

Summary

Harbor is an open-source cloud-native artifact registry that stores, signs, and scans container images and other artifacts. Harbor provides role-based access control, vulnerability scanning (via Trivy), image signing, replication between registries, and garbage collection. It is a CNCF Graduated project.

This Application Note explains how to use CloudCasa to back up and restore Harbor running in Kubernetes. Harbor is installed using the Harbor Helm chart. CloudCasa has been tested with Harbor installed using the Helm chart from the harbor Helm repository (https://helm.goharbor.io).

Note

Unlike some other applications covered in CloudCasa Application Notes, Harbor does not have an actively maintained Kubernetes operator. The Harbor operator was archived by the Harbor project in August 2025 due to low maintenance activity. The Helm chart is the only supported method for deploying Harbor on Kubernetes.

See also

Before you start

  • Use the internal database. The Harbor Helm chart can deploy with an internal PostgreSQL database (the default) or connect to an external one. This Application Note covers the internal database. If you use an external database, back it up separately using the appropriate method for your database provider.

  • Persistence must be enabled. CloudCasa backs up the data that Harbor has saved to disk on its Persistent Volumes (PVs). The Harbor Helm chart enables persistence by default for all components. If persistence has been explicitly disabled in the Helm chart values, re-enable it before using CloudCasa.

  • Understand Harbor’s components. The Harbor Helm chart deploys several components, each with its own PV:

    Component

    Workload type

    Data stored

    registry

    Deployment

    Container image layers and manifests

    database

    StatefulSet

    Internal PostgreSQL — stores all Harbor metadata (users, projects, repositories, tags, scan results, replication policies, labels, robot accounts, etc.)

    redis

    StatefulSet

    Session data and cache (ephemeral — regenerated automatically after restore)

    jobservice

    Deployment

    Asynchronous job logs (replication, garbage collection, scanning)

    Trivy

    StatefulSet

    Vulnerability database cache (re-downloaded automatically if missing)

    The registry PV holds the actual image data. The database PV holds the metadata that makes that data accessible and meaningful. Both must be backed up together.

Backup

It is recommended to define a full cluster backup, so that all resources Harbor needs, including any cluster-scoped resources, are included and can be restored together. If you are defining a more granular backup, ensure that the Harbor namespace is included, and that the option to backup PVs is enabled (which is the default).

A pre-backup App Hook is used to flush the internal PostgreSQL database to disk before CloudCasa takes the PV snapshot. Optionally, Harbor can be put into read-only mode during the backup to prevent image pushes while the snapshot is in progress.

Comparison of backup methods

Method

Consistency level

Write blocking

Post-hook needed

Complexity

Read-only mode + CHECKPOINT

Application-consistent

Yes — image pushes blocked until read-only mode is disabled

Yes

Higher

CHECKPOINT only

Crash-consistent

No — Harbor continues to accept pushes

No

Lower

Which method should you choose?

  • If you want the simplest setup and can tolerate a crash-consistent backup, use CHECKPOINT only (Method 2). PostgreSQL’s write-ahead logging ensures that the database is always recoverable from a PV snapshot, even if a small number of transactions were still in memory at the time of the snapshot. Image layers in the registry PV are immutable once written, so they are inherently safe to snapshot at any time.

  • If you want to guarantee that no image push is partially written to the registry PV during the snapshot, use Read-only mode + CHECKPOINT (Method 1). This puts Harbor into read-only mode before the backup, which prevents all image pushes (pulls continue to work). This is the most protective option, but it temporarily blocks push operations.

Step 1: Find your Harbor pods

Note the namespace where Harbor is running (the default is harbor), then list the pods and their labels:

kubectl get pods -n <harbor-namespace> --show-labels

Make a note of:

  • A label that uniquely identifies the Harbor database pod (e.g. component=database). To verify that the label works:

    kubectl get pods -n <harbor-namespace> -l <database-label>
    
  • The name of the container inside the database pod. To check:

    kubectl get pod <harbor-database-pod> -n <harbor-namespace> -o jsonpath='{.spec.containers[*].name}'
    
  • If you are using Method 1 (read-only mode), also identify the Harbor core pod (e.g. component=core):

    kubectl get pods -n <harbor-namespace> -l <core-label>
    

    Note the container name in the core pod as well:

    kubectl get pod <harbor-core-pod> -n <harbor-namespace> -o jsonpath='{.spec.containers[*].name}'
    

Step 2: Create the pre-backup App Hook

In CloudCasa, go to Configuration → App Hooks and click “Add App Hook”. Fill in the fields as follows.

Database CHECKPOINT hook (required for both methods):

Name: A name for the hook, for example harbor-db-pre-backup.

Type: Pre-backup

Pod selector: The label from Step 1, for example component = database.

Container name: The container name from Step 1, for example database.

Command:

sh -c 'psql -U postgres -d registry -c "CHECKPOINT;"'

This command:

  • Runs a PostgreSQL CHECKPOINT inside the Harbor database container using the postgres superuser. Because the hook runs inside the database container and connects via the local Unix socket, no password is needed.

  • A CHECKPOINT forces PostgreSQL to flush all dirty buffers from shared memory to the data files on disk and write a checkpoint record to the write-ahead log (WAL). This minimizes how much WAL needs to be replayed when the database starts after a restore.

  • The command connects to the registry database, which is the main database used by Harbor. PostgreSQL runs the CHECKPOINT at the server level regardless of which database the command connects to, so all databases in the instance are flushed.

Click Save.

Read-only mode hook (Method 1 only — skip this if you chose Method 2):

Create a second pre-backup App Hook:

Name: A name for the hook, for example harbor-readonly-pre-backup.

Type: Pre-backup

Pod selector: The label from Step 1, for example component = core.

Container name: The container name from Step 1, for example core.

Command:

sh -c 'curl -sf -X PUT -H "Content-Type: application/json" -u admin:${HARBOR_ADMIN_PASSWORD} -d "{\"read_only\":true}" http://127.0.0.1:8080/api/v2.0/configurations'

This command:

  • Uses the Harbor REST API to put the registry into read-only mode. While read-only mode is enabled, image pushes are rejected and garbage collection does not run. Image pulls continue to work normally.

  • Authenticates as the admin user with the password from the HARBOR_ADMIN_PASSWORD environment variable. The Harbor Helm chart stores this password in a Kubernetes Secret and injects it into the core container as an environment variable, so the hook command can use it without any additional configuration.

  • Connects to the Harbor core API at 127.0.0.1:8080 (the local core service inside the core container).

  • If the API call fails, the command returns an error.

Click Save.

Important: When defining the backup (Step 4), add the read-only mode hook before the CHECKPOINT hook, so that Harbor stops accepting pushes before the database is flushed.

Step 3: Create the post-backup App Hook

Skip this step if you chose Method 2 (CHECKPOINT only). Method 2 does not put Harbor in read-only mode and does not need a post-backup hook.

Create another App Hook with the following settings:

Name: A name for the hook, for example harbor-readonly-post-backup.

Type: Post-backup

Pod selector: Same label as the pre-backup hook.

Container name: Same container name as the pre-backup hook.

Command:

sh -c 'curl -sf -X PUT -H "Content-Type: application/json" -u admin:${HARBOR_ADMIN_PASSWORD} -d "{\"read_only\":false}" http://127.0.0.1:8080/api/v2.0/configurations'

Click Save.

What this command does:

  • It disables read-only mode on Harbor, allowing image pushes and garbage collection to resume.

  • This hook must run after the backup is complete to restore normal Harbor operations.

A post-backup App Hook is required for Method 1 because the pre-backup hook leaves Harbor in read-only mode. Without this hook, Harbor would remain read-only until an administrator manually disables it in the Harbor UI under Administration → Configuration → System Settings.

Note: A failed pre-backup App Hook does not stop the backup. The Harbor PVs are still backed up, but CloudCasa marks the backup job as Partially Failed, with a message such as “Backup partially successful. … app hooks failed”. If the CHECKPOINT hook fails, the backed-up data is still recoverable — PostgreSQL’s crash recovery automatically replays the WAL when the database starts from the restored PVs. Open the job on the Activity page and check its errors and warnings to see why the App Hook failed.

Step 4: Create a backup definition

Create a backup definition following the CloudCasa User Guide. Select a full cluster backup, and in the App Hooks section of the backup definition, add the pre-backup hook(s) created in Step 2. If you chose Method 1, also add the post-backup hook from Step 3.

When adding multiple pre-backup hooks (Method 1), add the read-only mode hook first and the CHECKPOINT hook second.

See also

For more details on defining a backup, see Defining a Kubernetes backup job.

Restore

Restore to a namespace or cluster where Harbor is not already running. Do not restore over a running Harbor deployment. When Harbor starts after the restore, PostgreSQL automatically runs crash recovery on the restored data files, replaying any pending transactions from the WAL to bring the database to a fully consistent state.

When creating the restore definition, select the namespace where Harbor is installed. PVs are included by default.

No operator or Custom Resource Definitions (CRDs) are needed. The restored Deployments and StatefulSets start the Harbor pods directly.

Important: If Harbor was backed up while in read-only mode (Method 1), it will still be in read-only mode after the restore. To disable it, go to Administration → Configuration → System Settings in the Harbor UI and uncheck Repository Read Only, or run the following command:

kubectl exec -n <harbor-namespace> <harbor-core-pod> -c <core-container> -- \
  sh -c 'curl -s -X PUT -H "Content-Type: application/json" -u "admin:${HARBOR_ADMIN_PASSWORD}" -d "{\"read_only\": false}" "http://127.0.0.1:8080/api/v2.0/configurations"'

Check the restored data

After the restore completes, check that all Harbor pods are running:

kubectl get pods -n <harbor-namespace>

All pods should be in the Running state. The database pod may take a moment to start while PostgreSQL runs crash recovery.

Then log in to the Harbor UI and verify:

  • Your projects and repositories are listed.

  • Image tags are present with correct digests.

  • You can pull an image that was present before the backup:

    docker pull <harbor-url>/<project>/<image>:<tag>
    
  • You can push a new image (after disabling read-only mode, if applicable):

    docker push <harbor-url>/<project>/<image>:<tag>
    

See also

For more information on defining a restore see Cluster Restore Wizard.

Troubleshooting

Harbor pods stuck in CrashLoopBackOff after restore

If the database pod is in CrashLoopBackOff after a restore, check its logs:

kubectl logs -n <harbor-namespace> <harbor-database-pod> -c <database-container>

If the logs show PostgreSQL crash recovery errors, the data files may be corrupted. This can happen if the PV snapshot was taken while the database was under heavy write load and no CHECKPOINT was performed. Ensure the CHECKPOINT pre-backup hook is configured correctly and completes successfully before the next backup.

If other pods (core, registry, jobservice) are in CrashLoopBackOff while the database pod is still starting, wait for the database pod to become ready first. The other components depend on the database and will retry their connections automatically.

Trivy scanner not working after restore

After a restore, the Trivy vulnerability scanner may need to re-download its vulnerability database. This happens automatically when the first scan is triggered. If scans fail with database errors, delete the Trivy pod to let it restart with a fresh download:

kubectl delete pod -n <harbor-namespace> -l <trivy-label>

Registry garbage collection after restore

After a restore, there may be unreferenced image layers in the registry storage if the backup captured the registry PV and the database PV at slightly different times. These orphaned layers consume storage but do not affect Harbor’s operation. Run garbage collection from the Harbor UI under Administration → Clean Up to reclaim the space.