This guide covers Harbor registry setup, configuration, storage, and usage.
Harbor is deployed as a container registry with:
- Local registry (
libraryproject) for pushing/pulling your own images - DockerHub proxy cache (
dockerhubproject) for caching Docker Hub images on-demand - Robot accounts for CI/CD automation
- PostgreSQL database (external, managed by CloudNativePG operator)
./scripts/harbor-setup.sh secretsThis creates the harbor-credentials secret with database and Redis passwords.
./scripts/harbor-setup.sh robotThis creates a robot account for CI/CD builds. The credentials will be saved to your .env file.
# First, create the DockerHub registry endpoint in Harbor UI:
# Administration → Registries → New Endpoint
# Name: DockerHub
# Type: Docker Hub
# Endpoint URL: https://registry-1.docker.io
# Then create the proxy cache project:
./scripts/harbor-setup.sh proxy./scripts/cert-setup.shThis installs the Harbor TLS certificate so Docker can verify it.
./scripts/harbor-test.shThis runs comprehensive tests for push/pull to both local registry and DockerHub cache.
- Project Name:
library - Status: Public, ready to use
- Purpose: Default registry for pushing/pulling images
- Full Name:
robot$library+ci-builder(example) - Secret: Stored in
.envfile (do not commit) - Project:
library - Permissions: Push, Pull, Create artifacts
- Purpose: Automated CI/CD builds and image pushes
Credentials stored in .env file (not committed to git):
HARBOR_ROBOT_ACCOUNT_NAME=ci-builder
HARBOR_ROBOT_ACCOUNT_SECRET=<your-secret-here>
HARBOR_ROBOT_ACCOUNT_FULL_NAME=robot$library+ci-builder.env file which is gitignored. Never commit actual secrets to git.
- Name:
DockerHub - Type:
docker-hub - URL:
https://registry-1.docker.io - Status: Configured and ready
- Project Name:
dockerhub - Status: Created and public
- Proxy Cache: ✅ ENABLED
According to the Harbor documentation, proxy cache can only be enabled when creating a project, not on an existing project. The project was recreated with proxy cache enabled from the start.
# Login with robot account
docker login harbor.dataknife.net \
-u 'robot$library+ci-builder' \
-p '${HARBOR_ROBOT_ACCOUNT_SECRET}'
# Tag and push an image
docker tag my-image:latest harbor.dataknife.net/library/my-image:latest
docker push harbor.dataknife.net/library/my-image:latestThe proxy cache is now enabled and ready to use:
# Pull an image through the proxy cache
# First pull: Harbor fetches from DockerHub and caches it
docker pull harbor.dataknife.net/dockerhub/library/nginx:latest
# For official images, always include 'library' namespace
docker pull harbor.dataknife.net/dockerhub/library/hello-world:latest
# Subsequent pulls: Served from Harbor's cache
docker pull harbor.dataknife.net/dockerhub/library/nginx:latestImportant: According to Harbor documentation, when pulling official images or from single-level repositories, you must include the library namespace. For example:
- Official Docker Hub image:
nginx:latest→harbor.dataknife.net/dockerhub/library/nginx:latest - Public Docker Hub image:
goharbor/harbor-core:dev→harbor.dataknife.net/dockerhub/goharbor/harbor-core:dev
# Pull an image you pushed to the library project
docker pull harbor.dataknife.net/library/my-image:latest| Component | Size | Percentage | Purpose |
|---|---|---|---|
| Registry | 400Gi | 80% | Container images (main storage) |
| Chartmuseum | 50Gi | 10% | Helm charts |
| Trivy | 40Gi | 8% | Vulnerability scans |
| Jobservice | 5Gi | 1% | Job data and logs |
| Redis | 5Gi | 1% | Cache data |
| Database | 1Gi | - | Note: PostgreSQL has separate 20Gi x 2 = 40Gi storage |
| PostgreSQL | 20Gi x 2 | - | Database (40Gi total) |
Total Harbor Storage: 501Gi (plus 40Gi PostgreSQL = 541Gi total)
- Storage Class:
truenas-csi-nfs(default) - Volume Expansion: ✅ Supported (
allowVolumeExpansion: true) - Access Mode: ReadWriteOnce (RWO)
The registry component stores all container images. Storage requirements depend on:
- Number of images: More images = more storage needed
- Image sizes: Larger images (e.g., ML models, databases) need more space
- Retention policies: How long images are kept
- Proxy cache usage: DockerHub proxy cache also uses registry storage
- Tag policies: Multiple tags per image increase storage
- Development/Testing: 20-50Gi
- Small Production: 100-200Gi
- Medium Production: 200-500Gi
- Large Production: 500Gi-2Ti or more
Update harbor/base/harbor-helmchart.yaml:
persistence:
persistentVolumeClaim:
registry:
size: 400Gi # Increase as neededCommit and push. Fleet will update the HelmChart, and Harbor will recreate the PVC with the new size.
resourcePolicy: "keep" is set. Consider backing up important images first.
If the storage class supports volume expansion (truenas-csi-nfs does), you can expand the PVC directly:
# Expand registry PVC
kubectl patch pvc harbor-registry -n harbor \
-p '{"spec":{"resources":{"requests":{"storage":"400Gi"}}}}'
# Wait for expansion to complete
kubectl wait --for=condition=FileSystemResizePending pvc/harbor-registry -n harbor --timeout=5m
# Restart registry pod to apply new size
kubectl rollout restart deployment harbor-registry -n harbor-
Edit the PVC:
kubectl edit pvc harbor-registry -n harbor
-
Update the
spec.resources.requests.storagefield -
Wait for expansion to complete
-
Restart the registry pod if needed
kubectl get pvc -n harbor -o custom-columns=NAME:.metadata.name,SIZE:.spec.resources.requests.storage,USED:.status.capacity.storage# Registry storage usage
kubectl exec -n harbor deployment/harbor-registry -c registry -- df -h /storage
# Chartmuseum storage usage
kubectl exec -n harbor deployment/harbor-chartmuseum -c chartmuseum -- df -h /chart_storage
# Trivy storage usage
kubectl exec -n harbor statefulset/harbor-trivy -c trivy -- df -h /var/lib/trivy- Go to Harbor UI: https://harbor.dataknife.net
- Navigate to Administration → Registries
- Check storage usage in project statistics
- Set retention policies: Automatically clean up old images
- Monitor usage: Set up alerts for storage thresholds
- Regular cleanup: Remove unused images and tags
- Use proxy cache wisely: DockerHub proxy cache can consume significant storage
- Plan for growth: Allocate more storage than initially needed
- Backup strategy: Regular backups of important images
# Use Harbor admin credentials from .env file or Harbor UI
curl -s -k -u "admin:${HARBOR_ADMIN_PASSWORD}" \
"https://harbor.dataknife.net/api/v2.0/projects" | \
jq -r '.[] | "\(.name): public=\(.metadata.public), proxy_cache=\(.registry_id // "none")"'curl -s -k -u "admin:${HARBOR_ADMIN_PASSWORD}" \
"https://harbor.dataknife.net/api/v2.0/projects/library/robots" | \
jq -r '.[] | "\(.name)"'curl -s -k -u "admin:${HARBOR_ADMIN_PASSWORD}" \
"https://harbor.dataknife.net/api/v2.0/registries" | \
jq -r '.[] | "\(.name): \(.type), URL=\(.url)"'curl -s -k -u "admin:${HARBOR_ADMIN_PASSWORD}" \
"https://harbor.dataknife.net/api/v2.0/projects?name=dockerhub" | \
jq -r '.[] | select(.name == "dockerhub") | {
name: .name,
project_id: .project_id,
registry_id: .registry_id,
proxy_cache_enabled: (if .registry_id != null and .registry_id != 0 then "YES" else "NO" end)
}'${HARBOR_ADMIN_PASSWORD} with your actual Harbor admin password from .env file or Harbor UI. Never commit passwords to git.
According to the Harbor documentation:
-
First pull: When a pull request comes to the proxy cache project and the image is not cached, Harbor pulls the image from Docker Hub and serves it as if it's a local image. The image is then cached for future requests.
-
Subsequent pulls: Harbor checks the image's latest manifest in Docker Hub:
- If unchanged: Serves from cache
- If updated: Pulls new image from Docker Hub, serves it, and caches it
- If Docker Hub unreachable: Serves from cache
- If image removed from Docker Hub: No image is served
-
Rate limiting: As of Harbor v2.1.1+, Harbor uses HEAD requests to check for updates, which doesn't trigger Docker Hub's rate limiter. Only actual pulls count towards the rate limit.
⚠️ Proxy cache can only be enabled when creating a project - it cannot be enabled on existing projects. This is why the option is greyed out in the UI for existing projects.- The robot account secret cannot be retrieved after creation - keep it secure
- Images are cached on-demand when you pull them through the proxy
- Official Docker Hub images require the
librarynamespace in the pull path - By default, Harbor creates a 7-day retention policy for proxy cache projects
- Proxy cache projects cannot receive pushes - they are read-only (pull from Docker Hub only)
- This is expected behavior - proxy cache can only be enabled when creating a project
- Solution: Delete the project and recreate it with proxy cache enabled using
./scripts/harbor-setup.sh proxy
- Verify proxy cache is enabled: Check that
registry_idis not null/0 - Check Harbor logs for errors
- Ensure the registry endpoint is reachable
- Verify you're using the correct pull path format
- Ensure Harbor version is v2.1.1 or later
- Consider adding Docker Hub credentials to the registry endpoint to increase rate limits
- Harbor v2.1.1+ uses HEAD requests which don't count towards rate limits
If a PVC is full:
- Immediate: Expand the PVC using Option 2 or 3 above
- Cleanup: Remove unused images and tags
- Review: Check retention policies and adjust if needed
If storage class doesn't support expansion:
- Check storage class:
kubectl get storageclass truenas-csi-nfs -o yaml - Look for
allowVolumeExpansion: true - If false, you'll need to recreate the PVC (backup data first)
When recreating PVCs:
- Ensure
resourcePolicy: "keep"is set in HelmChart - Backup important images before changes
- Use Harbor's replication feature to sync to another registry
- Export images before PVC recreation