🗺️ What is Harbor?

Harbor is a CNCF graduated open-source container registry that extends the open-source Docker Distribution (registry v2) with enterprise features: role-based access control, vulnerability scanning, image signing, replication across registries, proxy caching of upstream registries, and a rich web UI.

Running your own registry gives you control over image availability (no DockerHub rate limits), latency (images stored close to the cluster), and security policy enforcement.

Harbor Core API REST + UI backend Registry OCI / Docker v2 Portal (UI) Web dashboard Trivy / Clair Vuln scanner Notary / Cosign Image signing Replication Push/pull to remotes PostgreSQL Redis Blob Storage Jobservice docker push/pull CI / kubectl / skopeo Upstream Registries DockerHub / ECR / GCR Remote Harbors Geo-replication

🔒 RBAC & Projects

Multi-tenant projects with role-based access. Assign Guest, Developer, Maintainer, or Admin roles per project.

🔍 Vulnerability Scanning

Trivy or Clair scans images on push. Block deployments of vulnerable images via admission webhooks.

📡 Replication

Push or pull images to/from DockerHub, ECR, GCR, Quay, or other Harbor instances. Scheduled or event-driven.

🪞 Proxy Cache

Cache upstream registry pulls locally. Eliminates rate-limit errors and speeds up image pulls.

⚙️ Installing Harbor

Helm Install

helm repo add harbor https://helm.goharbor.io
helm repo update

helm install harbor harbor/harbor \
  --namespace harbor \
  --create-namespace \
  --version 1.14.0 \
  --set expose.type=ingress \
  --set expose.ingress.hosts.core=harbor.example.com \
  --set expose.ingress.hosts.notary=notary.example.com \
  --set expose.tls.enabled=true \
  --set expose.tls.certSource=secret \
  --set expose.tls.secret.secretName=harbor-tls \
  --set externalURL=https://harbor.example.com \
  --set harborAdminPassword=StrongP@ssw0rd \
  --set persistence.persistentVolumeClaim.registry.size=100Gi \
  --set persistence.persistentVolumeClaim.database.size=10Gi
💡 Use an external PostgreSQL and Redis in production Set database.type=external and redis.type=external to use managed database services. This simplifies backups and improves HA vs running them as in-cluster pods.

Projects and Repositories

Harbor organises images into Projects. A project can be public (no auth needed to pull) or private. Each project has independent RBAC, quotas, and scanning policies.

# After install, log in via CLI
docker login harbor.example.com -u admin -p StrongP@ssw0rd

# Tag and push an image to a project called "myteam"
docker tag myapp:latest harbor.example.com/myteam/myapp:latest
docker push harbor.example.com/myteam/myapp:latest

Robot Accounts for CI/CD

Robot accounts are non-human credentials for automated pipelines. They can be scoped to one project or system-wide.

# Create via Harbor REST API
curl -s -X POST \
  "https://harbor.example.com/api/v2.0/projects/myteam/robots" \
  -u "admin:StrongP@ssw0rd" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ci-robot",
    "duration": 30,
    "description": "CI/CD pipeline robot",
    "permissions": [{
      "kind": "project",
      "namespace": "myteam",
      "access": [
        {"resource": "repository", "action": "push"},
        {"resource": "repository", "action": "pull"},
        {"resource": "artifact",   "action": "read"}
      ]
    }]
  }'

ImagePullSecret for Kubernetes

# Create a pull secret from robot account credentials
kubectl create secret docker-registry harbor-pull-secret \
  --docker-server=harbor.example.com \
  --docker-username="robot\$myteam+ci-robot" \
  --docker-password="<token-from-robot-creation>" \
  --namespace=my-app

# Reference in a ServiceAccount (apply to all pods in namespace)
kubectl patch serviceaccount default \
  -n my-app \
  -p '{"imagePullSecrets": [{"name": "harbor-pull-secret"}]}'
kubectl patch serviceaccount default \ -n my-app \ -p '{"imagePullSecrets": [{"name": "harbor-pull-secret"}]}'

🔍 Vulnerability Scanning

Harbor integrates with Trivy (default) and Clair to scan OCI images for known CVEs. Scans can be triggered on push, scheduled, or manually.

Scan Policies

PolicyBehaviour
Scan on pushEvery pushed image is scanned automatically
Prevent vulnerable imagesBlock pull of images with severity ≥ Critical/High/Medium
CVE allowlistExempt specific CVE IDs from blocking (e.g. accepted risk)
Scheduled scanRe-scan existing images on a cron schedule (catches new CVEs)

Scan Results in API

# Trigger a manual scan
curl -s -X POST \
  "https://harbor.example.com/api/v2.0/projects/myteam/repositories/myapp/artifacts/latest/scan" \
  -u "admin:StrongP@ssw0rd"

# Get scan report
curl -s \
  "https://harbor.example.com/api/v2.0/projects/myteam/repositories/myapp/artifacts/latest?with_scan_overview=true" \
  -u "admin:StrongP@ssw0rd" | jq '.scan_overview'
⚠️ Admission webhook integration Combine Harbor's "prevent vulnerable images" policy with an OPA/Kyverno admission webhook to block deployment of images that haven't been scanned or have unresolved Critical CVEs — even if someone bypasses the registry directly.

Replication Rules

Harbor can replicate images to/from remote registries on a schedule or triggered by push events.

# Create a replication rule via API (push to ECR on every push)
curl -s -X POST \
  "https://harbor.example.com/api/v2.0/replication/policies" \
  -u "admin:StrongP@ssw0rd" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "push-to-ecr",
    "src_registry": {"id": 0},
    "dest_registry": {"id": 2},
    "dest_namespace": "myteam",
    "filters": [{"type": "name", "value": "myteam/**"}],
    "trigger": {"type": "event_based", "trigger_settings": {}},
    "deletion": false,
    "override": true,
    "enabled": true
  }'

Push Replication

Harbor pushes new images to a remote registry (ECR, GCR, Quay, another Harbor) immediately on push.

Pull Replication

Harbor periodically pulls images from a remote source and stores them locally.

Scheduled

Cron-triggered replication. Useful for syncing a curated base-image set during off-hours.

Geo-distribution

Replicate to Harbor instances in multiple regions for low-latency pulls from clusters worldwide.

Proxy Cache Projects

A Proxy Cache project transparently forwards image pulls to an upstream registry and caches the result locally — eliminating DockerHub rate limits and reducing pull latency.

# Configure imagePullPolicy to use Harbor as proxy for docker.io
# In Harbor UI: New Project → Proxy Cache → set upstream = https://registry-1.docker.io

# Then pull via Harbor proxy:
docker pull harbor.example.com/dockerhub-proxy/library/nginx:alpine
# Harbor fetches from DockerHub on first pull, serves from cache on subsequent pulls

# In Kubernetes — use the proxy address in pod spec:
# image: harbor.example.com/dockerhub-proxy/library/nginx:alpine
💡 Use proxy cache for all upstream registries Create proxy projects for docker.io, gcr.io, registry.k8s.io, and ghcr.io. Then update your cluster's default image pull paths. This also gives you an audit trail of every image pulled.

🔐 RBAC, Webhooks & Production Tips

Project Roles

RolePushPullDeleteManage members
Guest
Developer
Maintainer
Project Admin

LDAP / OIDC Integration

# Harbor supports LDAP, AD, and OIDC (e.g. Keycloak, Okta, Dex)
# Configure in Harbor UI → Administration → Configuration → Authentication

# For OIDC, key settings:
# OIDC Provider Name: Keycloak
# OIDC Endpoint:      https://keycloak.example.com/auth/realms/myrealm
# OIDC Client ID:     harbor
# OIDC Client Secret: <secret>
# OIDC Scope:         openid,profile,email,offline_access
# Group Claim Name:   groups    # maps OIDC groups → Harbor roles

Webhooks

Harbor emits webhook events for: image push, scan completion, image deletion, quota exceeded, replication status. Use these to trigger CI/CD pipelines or notify Slack.

# Example webhook payload on PUSH_ARTIFACT event
{
  "type": "PUSH_ARTIFACT",
  "occur_at": 1711234567,
  "operator": "ci-robot",
  "event_data": {
    "resources": [{
      "resource_url": "harbor.example.com/myteam/myapp:v1.2.3",
      "tag": "v1.2.3",
      "digest": "sha256:abc123..."
    }],
    "repository": {
      "name": "myapp",
      "full_name": "myteam/myapp",
      "type": "private"
    }
  }
}

Tag Immutability Rules

# Prevent overwriting existing tags (critical for reproducibility)
# In Harbor UI: Project → Tag Immutability → Add Rule
# Match tags: matches regex  v[0-9]+\.[0-9]+\.[0-9]+
# This makes semver tags immutable — re-pushing v1.2.3 is rejected

Tag Retention Policies

# Keep only the last 10 versions of any image (save storage)
# Harbor UI: Project → Tag Retention → Add Rule
# Retain: 10 most recently pushed
# Apply to: repositories matching **
# Untagged artifacts: delete after 7 days

Enable Content Trust

Require all images to be signed with Cosign/Notation before they can be pulled. Integrates with Kyverno for policy enforcement.

Quota Management

Set storage and artifact count quotas per project to prevent runaway storage consumption from rogue CI pipelines.

Audit Logs

Every push, pull, delete, and login is logged. Export to your SIEM for compliance and incident investigation.

Garbage Collection

Schedule GC jobs to reclaim space from deleted/untagged artifacts. Run during off-peak hours — GC locks the registry briefly.

📝 Knowledge Check

Q1. You want to prevent any image with a Critical CVE from being pulled from Harbor. Which two features must you configure?
  • A) Tag immutability + LDAP integration
  • B) Scan on push + "Prevent vulnerable images" policy
  • C) Proxy cache + webhook notifications
  • D) Robot account + replication rule
B) Scan on push + "Prevent vulnerable images" policy. Scan on push ensures every image is analysed immediately. The prevention policy then blocks pull of images whose highest CVE severity meets or exceeds the configured threshold (Critical, High, etc.).
Q2. A team's Kubernetes cluster is hitting DockerHub rate limits when pulling public images. What Harbor feature eliminates this?
  • A) Geo-replication to another Harbor
  • B) Push replication rule targeting docker.io
  • C) Proxy Cache project pointing to docker.io
  • D) Robot account with pull permissions to docker.io
C) Proxy Cache project. A Proxy Cache project routes pulls through Harbor, which fetches from DockerHub on first access and caches locally. Subsequent pulls never touch DockerHub — eliminating rate limit exposure entirely.
Q3. You accidentally pushed myapp:v2.0.0 with a bug and want to re-push a fixed version under the same tag. Harbor rejects the push. Why?
  • A) The robot account lacks Developer role
  • B) Tag immutability is enabled for semver tags in this project
  • C) The image is currently being scanned
  • D) The project storage quota has been exceeded
B) Tag immutability. When a tag immutability rule matches the tag (e.g. semver pattern v*.*.*), Harbor rejects any push that would overwrite an existing tag. The correct fix is to push a new tag (e.g. v2.0.1) with the fix.