Deploying Harbor on Kubernetes: A Step-by-Step Guide

DevOps & Cloud Engineer — building scalable, automated, and intelligent systems. Developer of sorts | Automator | Innovator
Harbor is a cloud-native registry that allows storing, signing, and scanning container images for vulnerabilities. Deploying Harbor on Kubernetes provides a scalable, highly available registry with integrated security features. In this guide, I will walk you through a full setup on a Kubernetes cluster using Helm, from installation to pushing your first Docker image.
Preparing for Installation
Before starting, ensure that your Kubernetes cluster is ready and Helm is installed. You should also have an ingress controller configured if you plan to expose Harbor externally.

Adding the Harbor Helm Repository
Helm simplifies deploying Harbor on Kubernetes. Begin by adding the Harbor Helm repository and fetching the chart:
helm repo add harbor https://helm.goharbor.io
helm repo update
helm fetch harbor/harbor --untar
Harbor provides detailed documentation for a high-availability deployment:
Harbor HA Helm Installation Guide.
Configuring Values for Your Deployment
Harbor is highly configurable through a values file. I created a values.yaml to suit my environment. Key configurations included:
Exposing Harbor via ingress with TLS enabled using the
pangolincertificate.Enabling persistence for Registry, Jobservice, and Trivy to ensure images are not lost during pod restarts.
Configuring internal components like Redis and PostgreSQL.
Values file excerpts:
expose:
type: ingress
tls:
enabled: true
certSource: pangolin
ingress:
hosts:
core: harbor.nyzex.in
className: "nginx"
externalURL: https://harbor.nyzex.in
persistence:
enabled: true
resourcePolicy: keep
persistentVolumeClaim:
registry:
size: 10Gi
jobservice:
jobLog:
size: 1Gi
trivy:
size: 5Gi
redis:
enabled: true
password: "redisStrongPassword"
harborAdminPassword: "Harbor12345"
We have disabled tls here, because it is being handled by pangolin for us. To understand that setup, please check my other blog:
Exposing Kubernetes Services Over the Internet Using MetalLB, NGINX Ingress, and Pangolin
Installing Harbor via Helm
Once your values file is ready, install Harbor in the harbor namespace:
kubectl create namespace harbor
helm install my-release ./harbor -n harbor -f values.yaml
Check the status of the pods and services:
kubectl get pods -n harbor
kubectl get svc -n harbor
kubectl get ingress -n harbor

Retrieving the Admin Password
Harbor generates an admin password automatically if not set. To retrieve it:
kubectl get secret my-release-harbor-core -n harbor -o jsonpath="{.data.HARBOR_ADMIN_PASSWORD}" | base64 --decode
This password allows you to log in to the Harbor UI at https://harbor.nyzex.in.
Fixing the Unauthorized Issue
After logging in, I encountered an unauthorized error while pushing images. The crucial fix was setting:
registry:
relativeurls: true
This change ensures Harbor correctly handles relative paths for the registry.
Docker Workflow
After logging in, I tested Harbor by building and pushing a sample Docker image.
Sample Dockerfile:
FROM busybox:latest
Build and tag the image:
docker build -t harbor.nyzex.in/myproj/test-image:latest .
Login to Harbor:
docker login harbor.nyzex.in
Push the image:
docker push harbor.nyzex.in/myproj/test-image:latest

The image successfully uploaded, and Harbor started scanning it if Trivy is enabled.

Understanding Harbor Components and Persistence

Harbor consists of several core components, each with its role:
Registry: Stores Docker images. Requires persistent storage to avoid losing images.
Core: Provides the UI, API, and handles authentication.
Jobservice: Executes background jobs, such as image replication or garbage collection.
Redis: Caching and session storage.
PostgreSQL: Stores metadata, configurations, and user information.
Trivy (optional): Performs vulnerability scans on images.
Persistent volumes ensure that each component retains data even if pods restart or are rescheduled.
kubectl get pvc -n harbor

Harbor Registry (my-release-harbor-registry)
Role: This is the core Docker registry. All images you push or pull are stored here.
Storage:
/storageinside the pod (mapped to PVCmy-release-harbor-registry).Growth: Every
docker pushincreases this PVC.Key takeaway: This is the main storage to monitor and expand if needed.
Redis (my-release-harbor-redis)
Role: Redis is used as cache and message broker for Harbor. It speeds up operations like:
Session management
Job queue for replication
Temporary caching of metadata
Storage: Minimal, mostly in-memory. PVC (
data-my-release-harbor-redis-0) is 1Gi because Redis mostly uses RAM.Growth: Usually does not grow with image pushes; only used for transient data.
Trivy (my-release-harbor-trivy)
Role: Trivy is Harbor’s vulnerability scanner. It scans container images for CVEs.
Storage: Stores vulnerability DB and scan cache in its PVC (
data-my-release-harbor-trivy-0).Growth: Increases if you scan many images, because it caches scan results and the CVE database (~5Gi here).
Key takeaway: You don’t need to increase this PVC unless you do tons of scans.
Database (my-release-harbor-database)
Role: Harbor’s PostgreSQL database. Stores:
Users, projects, and roles
Repository metadata
Scan results
Jobs, quotas, and configurations
Storage: Your PVC is small (1Gi). Actual usage is tiny at first.
Growth: Will grow slowly with metadata; pushing images does not increase it significantly.
Jobservice (my-release-harbor-jobservice)
Role: Manages asynchronous jobs for Harbor:
Image replication
Garbage collection
Scan jobs (Trivy)
Retention policies
Storage: Uses its PVC (
my-release-harbor-jobservice) minimally for job queues and logs (~1Gi).Growth: Typically small; no impact on image storage.
Summary:
| Component | PVC used | Grows with image push? | Notes |
| Registry | my-release-harbor-registry | Yes | This is your main image storage |
| Redis | data-my-release-harbor-redis-0 | No | Only cache, ephemeral data |
| Trivy | data-my-release-harbor-trivy-0 | Slightly | Stores scan DB & results |
| Database | database-data-my-release-harbor-database-0 | Minor | Stores metadata |
| Jobservice | my-release-harbor-jobservice | Minor | Handles background jobs |
Quick check of /storage
Now that we know /storage is to be monitored for usage, we can use the following to keep check on it:
kubectl exec -n harbor -it deployment/my-release-harbor-registry -- sh -c "du -sh /storage"
This gives total usage.
Check usage per subdirectory (blobs and repositories)
kubectl exec -n harbor -it deployment/my-release-harbor-registry -- sh -c "du -sh /storage/*"

Lessons Learned
The
registry.relativeurlsfix is crucial to avoid push failures.Persistence ensures images, logs, and scan data are retained safely.
Properly configuring ingress and TLS certificates is essential for secure access.
Redis and PostgreSQL are critical for Harbor functionality and must be monitored.
Docker login, build, and push are straightforward once Harbor is correctly configured.
Deploying Harbor with Helm on Kubernetes is straightforward if you follow these steps carefully. With persistence, security, and a proper workflow, Harbor becomes a reliable, enterprise-ready registry for your container images.






