Harbor
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)
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-namespacevalues.yaml
Expose
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.comCredentials
harborAdminPassword: Harbor12345 # initial admin password
secretKey: not-a-secure-key # 16-char key for encryption — change in prod
existingSecretAdminPassword: "" # reference an existing k8s Secret insteadPersistence
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: falseDatabase
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: disableRedis
redis:
type: internal # internal | external
external:
addr: redis.example.com:6379
# Sentinel HA: "sentinel1:26379,sentinel2:26379,sentinel3:26379"
sentinelMasterSet: ""
password: ""
existingSecret: ""
db: 0Internal TLS
internalTLS:
enabled: false
strong_ssl_ciphers: false
certSource: auto # auto | manual | secretComponents
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: 5m0sUpdate strategy
updateStrategy:
type: RollingUpdate # use Recreate if using RWO volumesLogging & Metrics
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
proxy:
httpProxy: ""
httpsProxy: ""
noProxy: 127.0.0.1,localhost,.local,.internal
components:
- core
- jobservice
- trivyCaching
cache:
enabled: false
expireHours: 24Network
ipv6:
enabled: true
ipv4:
enabled: trueAuthentication
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
- Create a Registry Endpoint pointing to the target registry
- Create a new project with Proxy Cache enabled, select the endpoint
- Optionally set a bandwidth limit (
-1= unlimited) - A 7-day default retention policy is applied automatically
Usage
docker pull harbor.example.com/<proxy-project>/library/nginx:latestHarbor 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
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:latestKubernetes imagePullSecret
kubectl create secret docker-registry harbor-secret \
--docker-server=harbor.example.com \
--docker-username='robot$myrobot' \
--docker-password=<token>