Harbor
5 min read
CNCF graduated open source container registry with role-based access control, vulnerability scanning (Trivy), image signing, replication across registries, proxy caching, and OCI artifact support.
Installation (Helm)
sh
helm repo add harbor https://helm.goharbor.io
helm fetch harbor/harbor --untar # extract values.yaml to customize
helm install harbor harbor/harbor -f values.yaml -n harbor --create-namespace
values.yaml
Expose
yaml
expose:
type: ingress # ingress | clusterIP | nodePort | loadBalancer | route
tls:
enabled: true
certSource: auto # auto | secret | none
commonName: ""
secret:
secretName: ""
ingress:
hosts:
core: harbor.example.com
className: ""
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
clusterIP:
httpPort: 80
httpsPort: 443
nodePort:
ports:
http: { port: 80, nodePort: 30002 }
https: { port: 443, nodePort: 30003 }
externalURL: https://harbor.example.com
Credentials
yaml
harborAdminPassword: Harbor12345 # initial admin password
secretKey: not-a-secure-key # 16-char key for encryption — change in prod
existingSecretAdminPassword: "" # reference an existing k8s Secret instead
Persistence
yaml
persistence:
enabled: true
resourcePolicy: keep # keep PVCs on helm uninstall
persistentVolumeClaim:
registry:
existingClaim: ""
storageClass: ""
accessMode: ReadWriteOnce # ReadWriteMany required for replicas > 1
size: 5Gi
jobservice:
jobLog:
storageClass: ""
size: 1Gi
database:
storageClass: ""
size: 1Gi
redis:
storageClass: ""
size: 1Gi
trivy:
storageClass: ""
size: 5Gi
imageChartStorage:
disableredirect: false
type: filesystem # filesystem | s3 | azure | gcs | swift | oss
filesystem:
rootdirectory: /storage
s3:
region: us-east-1
bucket: mybucket
accesskey: ""
secretkey: ""
# existingSecret: my-s3-secret
azure:
accountname: ""
accountkey: ""
container: ""
# existingSecret: my-azure-secret
gcs:
bucket: ""
encodedkey: "" # base64-encoded service account JSON
useWorkloadIdentity: false
Database
yaml
database:
type: internal # internal | external
# internal — bundled PostgreSQL
internal:
password: changeit
shmSizeLimit: 512Mi
# external — bring your own PostgreSQL 9.6+
external:
host: postgres.example.com
port: 5432
username: harbor
password: ""
coreDatabase: registry
existingSecret: ""
sslmode: disable
Redis
yaml
redis:
type: internal # internal | external
external:
addr: redis.example.com:6379
# Sentinel HA: "sentinel1:26379,sentinel2:26379,sentinel3:26379"
sentinelMasterSet: ""
password: ""
existingSecret: ""
db: 0
Internal TLS
yaml
internalTLS:
enabled: false
strong_ssl_ciphers: false
certSource: auto # auto | manual | secret
Components
yaml
portal:
replicas: 1
core:
replicas: 1
startupProbe:
enabled: true
quotaUpdateProvider: db # db | redis
jobservice:
replicas: 1
maxJobWorkers: 10
jobLoggers: file # file | database | stdout
loggerSweeperDuration: 14 # days
registry:
replicas: 1
upload_purging:
enabled: true
age: 168h # purge incomplete uploads older than this
trivy:
enabled: true
replicas: 1
vulnType: os,library
severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL
ignoreUnfixed: false
skipUpdate: false
timeout: 5m0s
Update strategy
yaml
updateStrategy:
type: RollingUpdate # use Recreate if using RWO volumes
Logging & Metrics
yaml
logLevel: info # debug | info | warning | error | fatal
metrics:
enabled: false
core:
path: /metrics
port: 8001
registry:
path: /metrics
port: 8001
jobservice:
path: /metrics
port: 8001
exporter:
path: /metrics
port: 8001
serviceMonitor:
enabled: false # requires monitoring.coreos.com/v1 CRD
interval: ""
Proxy
yaml
proxy:
httpProxy: ""
httpsProxy: ""
noProxy: 127.0.0.1,localhost,.local,.internal
components:
- core
- jobservice
- trivy
Caching
yaml
cache:
enabled: false
expireHours: 24
Network
yaml
ipv6:
enabled: true
ipv4:
enabled: true
Authentication
Authentication mode is set once — switching away from database mode after creating local users is not allowed.
| Mode | Description |
|---|---|
| Database | Accounts stored in Harbor's own database |
| LDAP / Active Directory | Accounts from external LDAP/AD server |
| OIDC | Accounts from an external OIDC provider |
Projects
Top-level namespace for repositories, artifacts, and access control.
Visibility
| Type | Access |
|---|---|
| Public | Anyone can pull (no auth required) |
| Private | Project members only |
A default public library project is created on first deploy.
RBAC Roles
| Role | Capabilities |
|---|---|
| Limited Guest | Read-only, cannot see full repo list |
| Guest | Pull images, view project content |
| Developer | Push/pull images, manage tags and labels |
| Maintainer | Manage repositories, robot accounts, webhooks, tag retention |
| Project Admin | Full project control including member management and scanner config |
Project Settings
Configurable per-project in the Configuration panel:
• Visibility — toggle public/private at any time
• Prevent vulnerable images from running — block pulls above a CVE severity threshold
• Automatically scan images on push — trigger Trivy on every push
• Content Trust — block unsigned images
• CVE Allowlist — per-project vulnerability exceptions
• Webhooks — event-driven notifications (push, delete, scan complete, etc.)
• Robot Accounts — scoped service credentials
• Tag Retention — policies to automatically clean up old tags
• Audit Logs — per-project activity trail
Vulnerability Scanning
Harbor uses Trivy by default; other scanners can be plugged in via the extensible scanner API.
Scan Triggers
• Manual — scan a single image or all images in a repository
• On push — automatic scan on image push (per-project setting)
• Scheduled — system-wide or per-project cron schedules
CVE Allowlists
Exemptions at the system level (applies to all projects) or overridden per project.
Vulnerability scanning of Cosign signatures is not supported.
Proxy Cache
Cache images from external registries to reduce bandwidth and avoid rate limiting.
Setup
1. Create a Registry Endpoint pointing to the target registry
2. Create a new project with Proxy Cache enabled, select the endpoint
3. Optionally set a bandwidth limit (-1 = unlimited)
4. A 7-day default retention policy is applied automatically
Usage
bash
docker pull harbor.example.com/<proxy-project>/library/nginx:latest
Harbor sends a HEAD request to check freshness before serving a cached image — avoids full re-downloads and rate limit hits.
Replication
Sync images and artifacts between Harbor and other registries (push or pull mode).
Triggers
• Manual — on demand
• Scheduled — cron-based
• Event-based — on push/delete events
Rules specify: source/destination endpoint, filters (name, tag, label, resource type), trigger, deletion propagation, bandwidth limit.
Robot Accounts
Automated service credentials for CI/CD pipelines.
• System robot accounts — access across multiple projects, managed under Administration
• Project robot accounts — scoped to a single project, managed in project settings
Permissions are selected explicitly at creation (push, pull, delete, scan, etc.). Tokens are shown once — save immediately.
Using Harbor as a Registry
bash
docker login harbor.example.com
docker tag myimage:latest harbor.example.com/myproject/myimage:latest
docker push harbor.example.com/myproject/myimage:latest
docker pull harbor.example.com/myproject/myimage:latest
Kubernetes imagePullSecret
bash
kubectl create secret docker-registry harbor-secret \
--docker-server=harbor.example.com \
--docker-username='robot$myrobot' \
--docker-password=<token>