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-namespace

values.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.com

Credentials

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

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

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

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

internalTLS:
  enabled: false
  strong_ssl_ciphers: false
  certSource: auto # auto | manual | secret

Components

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

updateStrategy:
  type: RollingUpdate # use Recreate if using RWO volumes

Logging & 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
    - trivy

Caching

cache:
  enabled: false
  expireHours: 24

Network

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

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

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

kubectl create secret docker-registry harbor-secret \
  --docker-server=harbor.example.com \
  --docker-username='robot$myrobot' \
  --docker-password=<token>