Harbor Deployment with CloudNativePG
5 minute read
Harbor Deployment with CloudNativePG on Kubernetes
This guide describes how to deploy Harbor on Kubernetes using a CloudNativePG (CNPG) PostgreSQL cluster managed by the CloudNativePG operator in an air‑gap and non-airgap environments.
Harbor Deployment with CloudNativePG in Non-airgap envoirnment
Prerequisites
- A running Kubernetes cluster.
- kubectl, kosi, and kubeopsctl installed and configured.
- You need to login with
kosi. Refer to the official KOSI documentation for details here.
Step 1 — Install CloudNativePG operator
Deploy the operator with Kosi:
kosi install --hub kubeops kubeops/cloudnative-pg-operator:<kubeopsctl_version> --dname cnpg-operator
With this step, it Installs the CloudNativePG operator into the cluster and the operator manages PostgreSQL clusters and their lifecycle.
Step 2 — Create PostgreSQL cluster for Harbor
1. Apply the following Cluster manifest to create a Postgres cluster with 2 instances and 1Gi storage:
cat <<EOF | kubectl apply -f -
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cloudnative-pg
namespace: harbor
spec:
instances: 2
imagePullSecrets:
- name: registry-pullsecret
storage:
size: 1Gi
EOF
2. Services and pods created for the cluster cloudnative-pg:
cloudnative-pg-rw → primary (read/write) cloudnative-pg-ro → replicas (read-only) cloudnative-pg-r → all pods
3. Verify pods are Running:
kubectl get pods -n harbor
Step 3 — Retrieve application user credentials
CNPG automatically creates a Secret named cloudnative-pg-app in the harbor namespace.
1. Verify the Secret exists:
kubectl get secret cloudnative-pg-app -n harbor
2. Decode the base64-encoded fields:
kubectl get secret cloudnative-pg-app -n harbor -o jsonpath="{.data.username}" | base64 -d
kubectl get secret cloudnative-pg-app -n harbor -o jsonpath="{.data.password}" | base64 -d
kubectl get secret cloudnative-pg-app -n harbor -o jsonpath="{.data.dbname}" | base64 -d
Example values (for illustration only):
username: app
password: Hw2t7hXuKPfZrVjVDwCc4PeKTevlB7ORmzQeW50JtEqiwHl40xkxuhVHeRIU3fX2
database: app
Important:
Use the non-superuser application credentials from this Secret in Harbor’s configuration.
Step 4 — Update Harbor tools.yaml for an external database
Edit your tools.yaml and set Harbor values under the helm chart configuration.
Example snippet:
- name: harbor
enabled: true
values:
standard:
namespace: harbor
harborpass: "password"
databasePassword: "<DB_PASSWORD>"
redisPassword: "Redis_Password"
externalURL: <your_domain_name>
nodePort: 30002
hostname: <your_domain_name>
harborPersistence:
persistentVolumeClaim:
registry:
size: 40Gi
storageClass: "rook-cephfs"
jobservice:
jobLog:
size: 1Gi
storageClass: "rook-cephfs"
database:
size: 1Gi
storageClass: "rook-cephfs"
redis:
size: 1Gi
storageClass: "rook-cephfs"
trivy:
size: 5Gi
storageClass: "rook-cephfs"
advanced:
database:
type: external
external:
host: "cloudnative-pg-rw.harbor.svc.cluster.local"
port: "5432"
username: "app"
password: "Hw2t7hXuKPfZrVjVDwCc4PeKTevlB7ORmzQeW50JtEqiwHl40xkxuhVHeRIU3fX2"
coreDatabase: "app"
Important: Use the -rw service host (cloudnative-pg-rw…) for write operations.
Do not use a superuser account.
Ensure the password matches the CNPG Secret.
Step 5 — Install Harbor with Kosi
1. Deploy Harbor using the updated tools.yaml:
kosi install --hub kubeops kubeops/harbor:2.0.3 -f tools.yaml --dname harbor
2. Verify Harbor pods:
kubectl get pods -n harbor
3. Access Harbor at: <your_domain_name>:30002 (or as configured)
Harbor Deployment with CloudNativePG in Airgap envoirnment
Prerequisites
- A running Kubernetes cluster.
- podman, kubectl, kosi, and kubeopsctl installed and configured.
- You need to login with
kosi. Refer to the official KOSI documentation for details here.
Step 1: Login into Harbor
First, log in to your Harbor registry and pull the CloudNativePG operator package using kosi.
podman login <ip_address>:<nodePort> -u <user_name> -p <password> --tls-verify=false
kosi pull --hub kubeops kubeops/cloudnative-pg-operator:<kubeopsctl_version> -o cloudnative-pg.tgz -r <ip_address>:<NodePort>/kubeops -t localhost:<NodePort>/kubeops
kosi install -p cloudnative-pg.tgz --dname cld-pg
Step 2: Manually Mirror the PostgreSQL Image to the KubeOps Project
In an air‑gapped environment, you must manually pull the PostgreSQL image from the external registry and push it to the KubeOps project in Harbor.
podman pull ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie
podman tag ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie <ip_address>:<NodePort>/kubeops/cloudnative-pg/postgresql:18.1-system-trixie
podman push <ip_address>:<NodePort>/kubeops/cloudnative-pg/postgresql:18.1-system-trixie --tls-verify=false
Step 3: Create the PostgreSQL Cluster for Harbor
Create a CNPG PostgreSQL cluster with two instances and 1Gi of storage in the harbor namespace:
cat <<EOF | kubectl apply -f -
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cloudnative-pg
namespace: harbor
spec:
instances: 2
storage:
size: 1Gi
EOF
Step 4: Configure the Cluster to Use the Mirrored Image
Update the CNPG cluster to use the image you pushed to Harbor in Step 2.
1.List the cluster:
kubectl get cluster -n harbor
2.Edit the cluster:
kubectl edit cluster -n harbor cloudnative-pg
3.Replace the image name in the editor:
From:
ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie
To:
localhost:<NodePort>/kubeops/cloudnative-pg/postgresql:18.1-system-trixie
Step 5: Retrieve CNPG Application User Credentials
CNPG automatically creates a Secret named cloudnative-pg-app in the harbor namespace.
1. Verify the Secret exists:
kubectl get secret cloudnative-pg-app -n harbor
2. Decode the base64-encoded fields:
kubectl get secret cloudnative-pg-app -n harbor -o jsonpath="{.data.username}" | base64 -d
kubectl get secret cloudnative-pg-app -n harbor -o jsonpath="{.data.password}" | base64 -d
kubectl get secret cloudnative-pg-app -n harbor -o jsonpath="{.data.dbname}" | base64 -d
Example values (for illustration only):
username: app
password: Hw2t7hXuKPfZrVjVDwCc4PeKTevlB7ORmzQeW50JtEqiwHl40xkxuhVHeRIU3fX2
database: app
Important:
Use the non-superuser application credentials from this Secret in Harbor’s configuration.
Step 6: Configure Harbor in enterprise.yaml
- Update your enterprise-values.yaml to configure Harbor with the external CNPG database.
Example snippet:
apiVersion: kubeops/kubeopsctl/enterprise/beta/v1
deleteNs: false
localRegistry: false
packages:
- name: harbor
enabled: true
values:
standard:
namespace: harbor
harborpass: "password"
databasePassword: "<DB_PASSWORD>"
redisPassword: "Redis_Password"
externalURL: <your_domain_name>
nodePort: 30002
hostname: <your_domain_name>
harborPersistence:
persistentVolumeClaim:
registry:
size: 40Gi
storageClass: "rook-cephfs"
jobservice:
jobLog:
size: 1Gi
storageClass: "rook-cephfs"
database:
size: 1Gi
storageClass: "rook-cephfs"
redis:
size: 1Gi
storageClass: "rook-cephfs"
trivy:
size: 5Gi
storageClass: "rook-cephfs"
advanced:
database:
type: external
external:
host: "cloudnative-pg-rw.harbor.svc.cluster.local"
port: "5432"
username: "app"
password: "Hw2t7hXuKPfZrVjVDwCc4PeKTevlB7ORmzQeW50JtEqiwHl40xkxuhVHeRIU3fX2"
coreDatabase: "app"
Important: Use the -rw service host (cloudnative-pg-rw…) for write operations.
Do not use a superuser account.
Ensure the password matches the CNPG Secret.
- Apply the updated configuration:
kubeopsctl apply -f enterprise-values.yaml
Step 6 — Verify the Harbor Deployment
- Check that Harbor pods are running in the harbor namespace:
kubectl get pods -n harbor
All Harbor pods should eventually reach a Running or Ready state
General Notes and best practices
- Always decode the CNPG app Secret to obtain the correct username, password, and database name used by Harbor.
- Use the cloudnative-pg-rw service for write traffic; use -ro services for reads if required.
- Production recommendations (follow your organizational security policies):
- Create a dedicated DB user (for example, harbor) rather than reusing the default app user.
- Enable TLS for Postgres connections.
- Implement backups (for example, CNPG Barman or object storage like S3).
- To scale the CNPG cluster, update the instances field in the Cluster spec and reapply.