Harbor
Last Updated: February 25, 2026
Overview
Harbor is an open-source, cloud-native container registry that provides enterprise-grade features for storing, signing, and scanning container images. As a CNCF Graduated project, Harbor has proven its maturity, security, and adoption across the industry.
Key Features
| Feature | Description |
|---|---|
| Multi-tenancy | Project-based isolation with RBAC |
| Vulnerability Scanning | Built-in Trivy scanner |
| Image Signing | Cosign and Notation support |
| Replication | Push/pull replication between registries |
| Garbage Collection | Automated cleanup of unused layers |
| Proxy Cache | Cache remote registries (Docker Hub, etc.) |
| Audit Logging | Complete operation history |
| OIDC/LDAP | Enterprise identity integration |
| Quota Management | Storage limits per project |
| Robot Accounts | Service accounts for automation |
Architecture
Component Responsibilities
| Component | Purpose |
|---|---|
| Core | API server, authentication, authorization, project management |
| Registry | Docker Distribution v2 API implementation, image storage |
| Job Service | Async task execution (replication, scanning, GC) |
| Trivy | Vulnerability scanning engine |
| Notary | Content trust and image signing (legacy) |
| Portal | Web UI for administration |
| PostgreSQL | Metadata, user data, audit logs |
| Redis | Job queue, session cache, registry cache |
Helm Installation
Prerequisites
# Add Harbor Helm repository
helm repo add harbor https://helm.goharbor.io
helm repo update
# Create namespace
kubectl create namespace harborBasic Installation
helm install harbor harbor/harbor \
--namespace harbor \
--set expose.type=ingress \
--set expose.ingress.hosts.core=harbor.example.com \
--set externalURL=https://harbor.example.com \
--set persistence.persistentVolumeClaim.registry.size=100Gi \
--set persistence.persistentVolumeClaim.database.size=10Gi \
--set persistence.persistentVolumeClaim.redis.size=5GiProduction values.yaml
# harbor-values.yaml
expose:
type: ingress
tls:
enabled: true
certSource: secret
secret:
secretName: harbor-tls
ingress:
hosts:
core: harbor.example.com
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
externalURL: https://harbor.example.com
# Persistence configuration
persistence:
enabled: true
resourcePolicy: keep
persistentVolumeClaim:
registry:
storageClass: gp3
size: 500Gi
accessMode: ReadWriteOnce
database:
storageClass: gp3
size: 20Gi
accessMode: ReadWriteOnce
redis:
storageClass: gp3
size: 10Gi
accessMode: ReadWriteOnce
trivy:
storageClass: gp3
size: 10Gi
accessMode: ReadWriteOnce
# Use S3 for registry storage (recommended for production)
# persistence:
# imageChartStorage:
# type: s3
# s3:
# region: us-east-1
# bucket: my-harbor-registry
# # Use IRSA for authentication
# # accesskey: ""
# # secretkey: ""
# Database configuration
database:
type: internal
internal:
resources:
requests:
memory: 512Mi
cpu: 250m
limits:
memory: 2Gi
cpu: 1000m
# For production, use external PostgreSQL
# type: external
# external:
# host: "harbor-db.cluster-xxx.us-east-1.rds.amazonaws.com"
# port: "5432"
# username: "harbor"
# password: "secretpassword"
# database: "harbor"
# sslmode: "require"
# Redis configuration
redis:
type: internal
internal:
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 1Gi
cpu: 500m
# For production, use external Redis (ElastiCache)
# type: external
# external:
# addr: "harbor-redis.xxx.ng.0001.use1.cache.amazonaws.com:6379"
# Core component
core:
replicas: 2
resources:
requests:
memory: 512Mi
cpu: 250m
limits:
memory: 2Gi
cpu: 1000m
# Secret key for encrypting credentials
secretKey: "not-a-secure-key" # Change this!
# XSRF key
xsrfKey: "not-a-secure-xsrf-key" # Change this!
# Job Service
jobservice:
replicas: 2
resources:
requests:
memory: 512Mi
cpu: 250m
limits:
memory: 2Gi
cpu: 1000m
jobLoggers:
- stdout
- database
maxJobWorkers: 10
# Registry
registry:
replicas: 2
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 2Gi
cpu: 1000m
credentials:
username: harbor_registry_user
password: harbor_registry_password # Change this!
# Trivy scanner
trivy:
enabled: true
replicas: 1
resources:
requests:
memory: 512Mi
cpu: 250m
limits:
memory: 2Gi
cpu: 1000m
# Trivy database auto-update
gitHubToken: "" # Optional: speeds up DB downloads
# Portal (Web UI)
portal:
replicas: 2
resources:
requests:
memory: 128Mi
cpu: 50m
limits:
memory: 512Mi
cpu: 500m
# Notary (optional, for Docker Content Trust)
notary:
enabled: false
# Metrics
metrics:
enabled: true
core:
path: /metrics
port: 8001
registry:
path: /metrics
port: 8001
jobservice:
path: /metrics
port: 8001
# Cache settings
cache:
enabled: true
expireHours: 24
# Garbage collection (manual trigger via UI/API)
# Configure automatic GC via cron job externally
# Log level
logLevel: info
# Update strategy
updateStrategy:
type: RollingUpdateInstallation with Custom Values
# Install with production values
helm install harbor harbor/harbor \
--namespace harbor \
--values harbor-values.yaml \
--wait
# Verify installation
kubectl get pods -n harbor
kubectl get ingress -n harbor
# Get initial admin password
kubectl get secret -n harbor harbor-core -o jsonpath="{.data.HARBOR_ADMIN_PASSWORD}" | base64 -dUpgrading Harbor
# Check current version
helm list -n harbor
# Update repo and check available versions
helm repo update
helm search repo harbor/harbor --versions
# Backup database before upgrade
kubectl exec -n harbor harbor-database-0 -- pg_dump -U postgres registry > harbor-backup.sql
# Upgrade
helm upgrade harbor harbor/harbor \
--namespace harbor \
--values harbor-values.yaml \
--waitProjects and RBAC
Project Types
| Type | Description | Use Case |
|---|---|---|
| Public | Anyone can pull images | Open source, shared base images |
| Private | Only members can access | Application images, internal tools |
Creating Projects
# Using Harbor API
# Login and get token
TOKEN=$(curl -s -u admin:Harbor12345 \
"https://harbor.example.com/service/token?service=harbor-registry" | jq -r .token)
# Create project via API
curl -X POST "https://harbor.example.com/api/v2.0/projects" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"project_name": "myapp",
"metadata": {
"public": "false",
"enable_content_trust": "true",
"auto_scan": "true",
"severity": "high"
},
"storage_limit": 107374182400
}'RBAC Roles
| Role | Permissions |
|---|---|
| Project Admin | Full control: manage members, policies, images |
| Maintainer | Push/pull images, scan, delete tags |
| Developer | Push/pull images, scan |
| Guest | Pull images only |
| Limited Guest | Pull images from proxy cache only |
Adding Members
# Add user to project
curl -X POST "https://harbor.example.com/api/v2.0/projects/myapp/members" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"role_id": 2,
"member_user": {
"username": "developer1"
}
}'
# Role IDs: 1=Admin, 2=Developer, 3=Guest, 4=Maintainer, 5=Limited GuestRobot Accounts
Robot accounts are service accounts for CI/CD automation:
# Create robot account
curl -X POST "https://harbor.example.com/api/v2.0/robots" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"name": "ci-robot",
"description": "Robot account for CI/CD pipelines",
"duration": 365,
"level": "project",
"permissions": [
{
"kind": "project",
"namespace": "myapp",
"access": [
{"resource": "repository", "action": "push"},
{"resource": "repository", "action": "pull"},
{"resource": "artifact", "action": "delete"},
{"resource": "tag", "action": "create"},
{"resource": "tag", "action": "delete"},
{"resource": "scan", "action": "create"}
]
}
]
}'
# Response includes robot name and secret
# {
# "name": "robot$ci-robot",
# "secret": "xxxxxxxxxxxxxxxxxxxx",
# ...
# }Using robot account in CI/CD:
# Docker login with robot account
docker login harbor.example.com \
-u 'robot$myapp+ci-robot' \
-p 'robot-secret-here'
# Push image
docker push harbor.example.com/myapp/app:v1.0.0Image Replication
Harbor supports bidirectional replication between registries.
Replication Modes
| Mode | Description | Use Case |
|---|---|---|
| Push | Harbor pushes to remote | Distribute to edge locations |
| Pull | Harbor pulls from remote | Mirror Docker Hub, sync from ECR |
Setting Up Replication Endpoints
# Create registry endpoint (e.g., Docker Hub)
curl -X POST "https://harbor.example.com/api/v2.0/registries" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"name": "docker-hub",
"type": "docker-hub",
"url": "https://hub.docker.com",
"credential": {
"type": "basic",
"access_key": "dockerhub-username",
"access_secret": "dockerhub-password"
}
}'
# Create registry endpoint (ECR)
curl -X POST "https://harbor.example.com/api/v2.0/registries" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"name": "aws-ecr",
"type": "aws-ecr",
"url": "https://123456789012.dkr.ecr.us-east-1.amazonaws.com",
"credential": {
"type": "basic",
"access_key": "AKIAIOSFODNN7EXAMPLE",
"access_secret": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
}'
# Create registry endpoint (another Harbor)
curl -X POST "https://harbor.example.com/api/v2.0/registries" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"name": "harbor-dr",
"type": "harbor",
"url": "https://harbor-dr.example.com",
"credential": {
"type": "basic",
"access_key": "admin",
"access_secret": "Harbor12345"
}
}'Creating Replication Rules
# Pull replication from Docker Hub (mirror official images)
curl -X POST "https://harbor.example.com/api/v2.0/replication/policies" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"name": "mirror-dockerhub-nginx",
"src_registry": {"id": 1},
"dest_namespace": "dockerhub-mirror",
"filters": [
{"type": "name", "value": "library/nginx"},
{"type": "tag", "value": "1.*"}
],
"trigger": {
"type": "scheduled",
"trigger_settings": {
"cron": "0 0 * * *"
}
},
"enabled": true,
"deletion": false,
"override": true,
"speed": -1
}'
# Push replication to DR site
curl -X POST "https://harbor.example.com/api/v2.0/replication/policies" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"name": "replicate-to-dr",
"dest_registry": {"id": 3},
"src_namespaces": ["production"],
"filters": [
{"type": "tag", "value": "v*"}
],
"trigger": {
"type": "event_based"
},
"enabled": true,
"deletion": true,
"override": true,
"speed": 102400
}'Replication Filters
| Filter Type | Description | Example |
|---|---|---|
name | Repository name pattern | library/**, myapp/* |
tag | Tag pattern | v*, latest, !*-dev |
label | Harbor label | production, approved |
resource | Resource type | image, chart |
Vulnerability Scanning
Trivy Integration
Harbor uses Trivy as its default vulnerability scanner. Trivy scans for:
- OS package vulnerabilities (Alpine, Debian, Ubuntu, RHEL, etc.)
- Application dependencies (npm, pip, gem, maven, go modules)
- Misconfigurations
Scan-on-Push Configuration
# Enable automatic scanning for a project
curl -X PUT "https://harbor.example.com/api/v2.0/projects/myapp" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"metadata": {
"auto_scan": "true"
}
}'Manual Scanning
# Trigger scan via API
curl -X POST "https://harbor.example.com/api/v2.0/projects/myapp/repositories/app/artifacts/sha256:abc123/scan" \
-u admin:Harbor12345
# Get scan results
curl "https://harbor.example.com/api/v2.0/projects/myapp/repositories/app/artifacts/sha256:abc123?with_scan_overview=true" \
-u admin:Harbor12345CVE Allowlists
Create allowlists to ignore specific CVEs:
# Set project-level CVE allowlist
curl -X PUT "https://harbor.example.com/api/v2.0/projects/myapp" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"cve_allowlist": {
"items": [
{"cve_id": "CVE-2023-12345"},
{"cve_id": "CVE-2023-67890"}
],
"expires_at": 1735689600
}
}'
# System-wide CVE allowlist
curl -X PUT "https://harbor.example.com/api/v2.0/system/CVEAllowlist" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"items": [
{"cve_id": "CVE-2023-12345"}
],
"expires_at": null
}'Preventing Vulnerable Image Deployment
Configure Harbor to block pulls of vulnerable images:
# Set vulnerability prevention policy
curl -X PUT "https://harbor.example.com/api/v2.0/projects/myapp" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"metadata": {
"prevent_vul": "true",
"severity": "high"
}
}'
# Severity options: none, low, medium, high, criticalWhen enabled, Harbor returns 412 Precondition Failed for pulls of images exceeding the severity threshold.
Image Signing
Harbor supports image signing with Cosign and Notation for supply chain security.
Cosign Integration
# Generate Cosign key pair
cosign generate-key-pair
# Sign image
cosign sign --key cosign.key harbor.example.com/myapp/app:v1.0.0
# Verify signature
cosign verify --key cosign.pub harbor.example.com/myapp/app:v1.0.0
# Sign with keyless (Sigstore/Fulcio)
COSIGN_EXPERIMENTAL=1 cosign sign harbor.example.com/myapp/app:v1.0.0Configuring Cosign in Harbor
# harbor-values.yaml
core:
# Enable Cosign signature verification
cosignKeyFile: /etc/cosign/cosign.pub
# Mount Cosign public key
extraVolumes:
- name: cosign-key
secret:
secretName: cosign-public-key
extraVolumeMounts:
- name: cosign-key
mountPath: /etc/cosign
readOnly: trueNotation (Notary v2) Integration
# Install Notation CLI
curl -sSL https://github.com/notaryproject/notation/releases/download/v1.0.0/notation_1.0.0_linux_amd64.tar.gz | tar xz
# Generate certificate
notation cert generate-test --default "mycompany.io"
# Sign image
notation sign harbor.example.com/myapp/app:v1.0.0
# Verify signature
notation verify harbor.example.com/myapp/app:v1.0.0Kubernetes Policy Enforcement
Use Kyverno or OPA Gatekeeper to enforce signed images:
# Kyverno policy for Cosign verification
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-cosign-signature
match:
resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "harbor.example.com/*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----Air-Gap Scenarios
Harbor is ideal for disconnected or air-gapped environments.
Offline Installer
# Download offline installer
wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz
# Transfer to air-gapped environment
# ... (USB, secure file transfer, etc.)
# Extract and install
tar xzvf harbor-offline-installer-v2.10.0.tgz
cd harbor
# Configure harbor.yml
cp harbor.yml.tmpl harbor.yml
vim harbor.yml
# Install
./install.sh --with-trivyharbor.yml Configuration
# harbor.yml for air-gap deployment
hostname: harbor.internal.local
http:
port: 80
https:
port: 443
certificate: /data/cert/server.crt
private_key: /data/cert/server.key
harbor_admin_password: Harbor12345
database:
password: root123
max_idle_conns: 100
max_open_conns: 900
data_volume: /data
trivy:
ignore_unfixed: false
skip_update: true # Important for air-gap
offline_scan: true # Important for air-gap
security_check: vuln
insecure: false
jobservice:
max_job_workers: 10
notification:
webhook_job_max_retry: 10
log:
level: info
local:
rotate_count: 50
rotate_size: 200M
location: /var/log/harbor
proxy:
http_proxy:
https_proxy:
no_proxy:
components:
- core
- jobservice
- trivyPreloading Images for Air-Gap Kubernetes
# On connected environment: Export images from Docker Hub
IMAGES=(
"nginx:1.25"
"redis:7"
"postgres:15"
"busybox:1.36"
)
for img in "${IMAGES[@]}"; do
docker pull $img
docker save $img -o $(echo $img | tr '/:' '-').tar
done
# Create archive
tar czvf k8s-images.tar.gz *.tar
# Transfer to air-gapped environment
# ... (USB, secure file transfer, etc.)
# On air-gapped environment: Load and push to Harbor
tar xzvf k8s-images.tar.gz
for tarfile in *.tar; do
docker load -i $tarfile
done
# Login to Harbor
docker login harbor.internal.local
# Re-tag and push
for img in "${IMAGES[@]}"; do
new_tag="harbor.internal.local/library/$img"
docker tag $img $new_tag
docker push $new_tag
doneTrivy Database Update for Air-Gap
# On connected environment: Download Trivy DB
trivy image --download-db-only
tar czvf trivy-db.tar.gz ~/.cache/trivy/db
# Transfer to air-gapped environment
# ...
# Extract to Harbor Trivy container
kubectl cp trivy-db.tar.gz harbor/harbor-trivy-0:/home/scanner/.cache/trivy/
kubectl exec -n harbor harbor-trivy-0 -- tar xzvf /home/scanner/.cache/trivy/trivy-db.tar.gz -C /home/scanner/.cache/trivy/Automated Image Sync Script
#!/bin/bash
# sync-images-to-harbor.sh
# Sync images from a manifest to Harbor
HARBOR_URL="harbor.internal.local"
PROJECT="library"
MANIFEST_FILE="required-images.txt"
# Login to Harbor
docker login $HARBOR_URL
# Read manifest and sync
while IFS= read -r image; do
[[ "$image" =~ ^#.*$ ]] && continue # Skip comments
[[ -z "$image" ]] && continue # Skip empty lines
echo "Processing: $image"
# Pull from source (if connected) or load from local
if docker pull $image 2>/dev/null; then
echo " Pulled from remote"
elif [ -f "images/$(echo $image | tr '/:' '-').tar" ]; then
docker load -i "images/$(echo $image | tr '/:' '-').tar"
echo " Loaded from local archive"
else
echo " ERROR: Cannot find image $image"
continue
fi
# Determine target name
if [[ "$image" == *"/"* ]]; then
repo_name=$(echo $image | cut -d'/' -f2-)
else
repo_name="library/$image"
fi
target="${HARBOR_URL}/${PROJECT}/${repo_name}"
# Re-tag and push
docker tag $image $target
docker push $target
echo " Pushed to $target"
done < "$MANIFEST_FILE"Sample manifest file:
# required-images.txt
# Base images
nginx:1.25
redis:7-alpine
postgres:15-alpine
# Kubernetes components
registry.k8s.io/ingress-nginx/controller:v1.9.0
registry.k8s.io/metrics-server/metrics-server:v0.6.4
# Monitoring
grafana/grafana:10.0.0
prom/prometheus:v2.47.0Harbor + Kubernetes Integration
Creating imagePullSecrets
# Create secret for Harbor
kubectl create secret docker-registry harbor-secret \
--docker-server=harbor.example.com \
--docker-username=robot\$myapp+k8s \
--docker-password=robot-secret \
-n default
# Verify
kubectl get secret harbor-secret -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jqServiceAccount Configuration
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
imagePullSecrets:
- name: harbor-secret
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
serviceAccountName: app-sa
containers:
- name: app
image: harbor.example.com/myapp/app:v1.0.0Proxy Cache Configuration
Configure Harbor as a proxy cache for external registries:
# Create proxy cache project via API
curl -X POST "https://harbor.example.com/api/v2.0/projects" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"project_name": "dockerhub-proxy",
"registry_id": 1,
"metadata": {
"public": "true"
}
}'Configure containerd to use Harbor proxy:
# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://harbor.example.com/v2/dockerhub-proxy/"]
[plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.example.com".auth]
username = "robot$proxy+pull"
password = "robot-secret"Garbage Collection
Harbor accumulates unused image layers over time. Configure garbage collection:
# Trigger GC manually via API
curl -X POST "https://harbor.example.com/api/v2.0/system/gc/schedule" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"schedule": {
"type": "Manual"
},
"parameters": {
"delete_untagged": true,
"dry_run": false
}
}'
# Schedule automatic GC
curl -X POST "https://harbor.example.com/api/v2.0/system/gc/schedule" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"schedule": {
"type": "Weekly",
"cron": "0 0 0 * * 0"
},
"parameters": {
"delete_untagged": true,
"dry_run": false
}
}'
# Check GC history
curl "https://harbor.example.com/api/v2.0/system/gc" \
-u admin:Harbor12345Tag Retention Policies
# Create retention policy
curl -X POST "https://harbor.example.com/api/v2.0/retentions" \
-H "Content-Type: application/json" \
-u admin:Harbor12345 \
-d '{
"algorithm": "or",
"rules": [
{
"disabled": false,
"action": "retain",
"scope_selectors": {
"repository": [
{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}
]
},
"tag_selectors": [
{"kind": "doublestar", "decoration": "matches", "pattern": "v*"}
],
"params": {
"latestPushedK": 10
}
},
{
"disabled": false,
"action": "retain",
"scope_selectors": {
"repository": [
{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}
]
},
"tag_selectors": [
{"kind": "doublestar", "decoration": "matches", "pattern": "**"}
],
"params": {
"nDaysSinceLastPush": 30
}
}
],
"trigger": {
"kind": "Schedule",
"settings": {
"cron": "0 0 0 * * *"
}
},
"scope": {
"level": "project",
"ref": 1
}
}'Best Practices
High Availability
# harbor-values.yaml for HA
core:
replicas: 3
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
component: core
topologyKey: kubernetes.io/hostname
registry:
replicas: 3
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
component: registry
topologyKey: kubernetes.io/hostname
portal:
replicas: 2
jobservice:
replicas: 2
# Use external PostgreSQL (RDS) and Redis (ElastiCache)
database:
type: external
external:
host: harbor-db.cluster-xxx.us-east-1.rds.amazonaws.com
port: "5432"
username: harbor
password: secretpassword
database: harbor
sslmode: require
redis:
type: external
external:
addr: harbor-redis.xxx.cache.amazonaws.com:6379
# Use S3 for registry storage
persistence:
imageChartStorage:
type: s3
s3:
region: us-east-1
bucket: harbor-registry-prod
regionendpoint: s3.us-east-1.amazonaws.com
encrypt: trueSecurity Hardening
- Enable TLS everywhere
- Use OIDC/LDAP instead of local users
- Implement network policies
- Regular security updates
- Audit log monitoring
# Network policy for Harbor
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: harbor-network-policy
namespace: harbor
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
harbor-access: "true"
ports:
- port: 443
egress:
- to:
- namespaceSelector: {}
ports:
- port: 443
- port: 5432 # PostgreSQL
- port: 6379 # RedisBackup Strategy
#!/bin/bash
# harbor-backup.sh
BACKUP_DIR="/backups/harbor/$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR
# Backup PostgreSQL
kubectl exec -n harbor harbor-database-0 -- \
pg_dump -U postgres registry > $BACKUP_DIR/harbor-db.sql
# Backup Harbor configuration
kubectl get configmap -n harbor -o yaml > $BACKUP_DIR/configmaps.yaml
kubectl get secret -n harbor -o yaml > $BACKUP_DIR/secrets.yaml
# Backup registry storage (if using PVC)
# For S3, enable versioning instead
# Compress
tar czvf $BACKUP_DIR.tar.gz $BACKUP_DIR
# Upload to S3 (if connected)
aws s3 cp $BACKUP_DIR.tar.gz s3://harbor-backups/
echo "Backup completed: $BACKUP_DIR.tar.gz"Summary
Harbor provides a comprehensive, enterprise-grade container registry solution that excels in:
- Security: Built-in vulnerability scanning, image signing, and RBAC
- Flexibility: Self-hosted with full control over data and infrastructure
- Air-Gap Support: Designed for disconnected environments
- Multi-tenancy: Project-based isolation with fine-grained access control
- Integration: Replication to/from any OCI-compliant registry
When to Choose Harbor
| Scenario | Recommendation |
|---|---|
| Air-gapped environment | Harbor (only option) |
| Strict data sovereignty | Harbor |
| Multi-cloud deployment | Harbor |
| AWS-native with EKS | Consider ECR first, Harbor for advanced features |
| Small team, limited ops | Docker Hub or ECR |
| Enterprise with compliance | Harbor or ECR |
Key Takeaways
- Plan storage carefully: Use S3-compatible storage for production
- Configure replication: Set up DR replication from day one
- Enable scanning: Auto-scan on push with severity thresholds
- Use robot accounts: Never use personal credentials in CI/CD
- Implement retention: Configure garbage collection and retention policies
- Monitor and alert: Use Prometheus metrics and alerting
- Backup regularly: Database backups are critical for disaster recovery