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.

ModeDescription
DatabaseAccounts stored in Harbor's own database
LDAP / Active DirectoryAccounts from external LDAP/AD server
OIDCAccounts from an external OIDC provider

Projects

Top-level namespace for repositories, artifacts, and access control.

Visibility

TypeAccess
PublicAnyone can pull (no auth required)
PrivateProject members only

A default public library project is created on first deploy.

RBAC Roles

RoleCapabilities
Limited GuestRead-only, cannot see full repo list
GuestPull images, view project content
DeveloperPush/pull images, manage tags and labels
MaintainerManage repositories, robot accounts, webhooks, tag retention
Project AdminFull 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>