Harbor Production Architecture

Self-hosted container registry — deployment, HA, security scanning, replication & operations

01

Overview

Harbor is an open-source container registry that stores, signs, and scans container images. It is a CNCF Graduated project (since June 2020), which puts it in the same maturity tier as Kubernetes, Prometheus, and Envoy. The latest stable release is v2.14.x (as of early 2026), with v2.15 in release candidate. Harbor is fully OCI-compliant, meaning it works with any OCI-compatible client — Docker, Podman, Buildah, Helm, and OCI artifact tooling.

The core value proposition of Harbor is control. When you self-host a registry, you control where images live, who can access them, what security policies are enforced, and how images are distributed across environments. This matters enormously for air-gapped environments, regulated industries, multi-datacenter architectures, and organizations that need to reduce their dependency on external services.

Why self-host a registry?

  • Air-gapped environments — Government, defense, financial, and healthcare systems that cannot reach the public internet need a local registry
  • Compliance — Regulations like FedRAMP, HIPAA, PCI-DSS may require that container images are scanned, signed, and stored within your boundary
  • Bandwidth and latency — Pulling from Docker Hub or cloud registries over the internet is slow, rate-limited, and unreliable at scale. A local registry eliminates external dependency
  • Supply chain security — Vulnerability scanning, image signing (Cosign), and immutable tags give you provable guarantees about what runs in production
  • Cost — Cloud registry egress charges add up fast at scale. Self-hosted is free beyond infrastructure

Strengths

  • CNCF Graduated — production-proven, large community
  • Built-in Trivy vulnerability scanning
  • Image signing with Cosign (Sigstore); Notary v1 removed in v2.9
  • Project-level RBAC with OIDC/LDAP integration
  • Cross-registry replication (push & pull modes)
  • Proxy cache for Docker Hub, GCR, Quay, GitHub Container Registry, etc.
  • Helm chart repository support
  • Fully open source — no enterprise edition, no feature gating

Considerations

  • No built-in clustering — HA requires external orchestration
  • PostgreSQL and Redis are hard dependencies
  • Storage can grow rapidly without retention policies
  • Garbage collection historically required read-only mode; non-blocking GC available since v2.1 but monitor carefully
  • Upgrade path requires attention — database migrations are involved
  • Web UI is functional but not as polished as commercial alternatives

Registry comparison

FeatureHarborDocker HubECR/GCR/ACRQuay
Self-hostedYes (only option)NoNo (managed)Yes + managed
LicenseApache 2.0ProprietaryProprietaryApache 2.0
Vulnerability scanningTrivy (built-in)Docker ScoutVaries by providerClair
Image signingCosign (Notary removed in v2.9)Docker Content TrustProvider-specificCosign support
ReplicationPush + pull modesN/AWithin providerGeo-replication
Proxy cacheYesN/AECR pull-throughYes (since Quay 3.7)
Air-gappedFully supportedNoNoYes (self-hosted)
CostFree (infra only)Free tier + paidPer-storage + egressFree + RHEL sub
Positioning

Harbor is the default choice for self-hosted container registries in the Kubernetes ecosystem. If a client needs a private registry and is not locked into a specific cloud provider, Harbor is the answer. The main open-source competitor is Quay (Red Hat), which is a capable alternative but more complex to deploy and more tightly integrated with the Red Hat ecosystem. For clients already deep in AWS/GCP/Azure, the managed registries (ECR/GCR/ACR) are simpler but sacrifice portability and air-gap capability.

02

Architecture

Harbor is a multi-component system. Understanding the component layout is critical for troubleshooting, capacity planning, and designing HA deployments.

+------------------------------------------------------------------+ | Clients | | (docker push/pull, helm push, podman, buildah) | +-------------------------------+----------------------------------+ | v +-------------------------------+----------------------------------+ | Nginx Proxy | | (TLS termination, request routing) | +-----+----------+---------+----------+---------+---------+ | | | | | v v v v v +--------+ +--------+ +--------+ +--------+ +------+ | Core | | Portal | |Registry| | Job | | Trivy| |Service | | (Web | |(Distri-| |Service | |Scan- | | | | UI) | | bution)| | | | ner | +---+----+ +--------+ +---+----+ +---+----+ +--+---+ | | | | v v v v +--------+ +----------+ +--------+ +--------+ | Post- | | Storage | | Redis | | Trivy | | greSQL | | Backend | |(Queue +| | DB | | (DB) | |(S3/GCS/ | | Cache) | |(vuln | | | | FS/Blob) | | | | data) | +--------+ +----------+ +--------+ +--------+

Core components

ComponentRoleNotes
Nginx ProxyReverse proxy & TLSTerminates TLS, routes requests to core, portal, or registry. All external traffic enters through Nginx.
Core ServiceAPI & business logicAuthentication, authorization, project management, replication, webhook notifications. The brain of Harbor.
PortalWeb UIAngular-based frontend for managing projects, images, users, and policies. Talks to Core via API.
RegistryImage storage (Distribution)The CNCF Distribution (formerly Docker Distribution, registry v2) engine. Handles actual image push/pull at the blob and manifest level.
Job ServiceAsync task runnerRuns replication, garbage collection, scan jobs, retention policy execution. Queue-based via Redis.
PostgreSQLMetadata databaseStores projects, users, RBAC rules, replication policies, scan results, audit logs. Critical stateful component.
RedisCache & job queueSession cache, job queue for the Job Service, temporary data. Losing Redis is recoverable but causes a brief disruption.
TrivyVulnerability scannerScans images for CVEs on push or on demand. Downloads vulnerability database from GitHub (or offline DB for air-gap).
NotaryImage signing (removed)TUF-based content trust. Deprecated in v2.6 and removed in v2.9. Superseded by Cosign. Not available in current Harbor versions.

Request flow

When a client runs docker push registry.example.com/myproject/myapp:v1.2:

  1. Nginx receives the request, terminates TLS, and routes it
  2. Core Service handles authentication (checks credentials against the local DB, LDAP, or OIDC provider)
  3. Core Service checks RBAC — does this user/robot account have push access to the myproject project?
  4. If authorized, the request is proxied to the Registry (Distribution) component
  5. Registry stores image layers (blobs) in the configured storage backend (S3, filesystem, etc.)
  6. If scan-on-push is enabled, Core creates a scan job in Redis, which Job Service picks up and sends to Trivy
  7. Trivy downloads the image layers, scans for vulnerabilities, and reports results back to Core via the database
Key insight

Harbor's Core Service sits in front of the standard CNCF Distribution registry and adds the features that Distribution lacks: authentication, RBAC, scanning, replication, and UI. The Registry component itself is the same distribution/distribution project (now a CNCF project) used by Docker Hub, GitHub Container Registry, and others. Harbor wraps it with enterprise features.

03

Deployment Models

Harbor provides an official installer that generates Docker Compose or Helm configurations. The deployment model you choose depends on your infrastructure, scale, and HA requirements.

Dev/Small Docker Compose

  • Single-host deployment with all components as containers
  • Built-in PostgreSQL and Redis (no external deps needed)
  • Official installer script generates docker-compose.yml
  • Good for dev/staging, small teams (< 50 users)
  • No HA — single point of failure on every component
  • Storage: local filesystem or S3-compatible backend

Production Helm Chart on K8s

  • Official Helm chart deploys all components as K8s workloads
  • Scales stateless components (core, portal, job service) via replicas
  • External PostgreSQL and Redis recommended for production
  • Ingress controller handles TLS and routing
  • Persistent volumes for registry storage (or S3 backend)
  • Best option for production Kubernetes environments

Legacy VM-Based

  • Docker Compose on a dedicated VM (or multiple VMs)
  • Common in environments without Kubernetes
  • Can still achieve HA with external DB, Redis, shared storage, and a load balancer
  • More operational burden than Helm-based deployment

Air-Gapped Offline Install

  • Harbor installer includes offline bundle with all container images
  • Trivy needs offline vulnerability database (download separately, transfer via sneakernet)
  • No internet required after initial setup
  • Common in government and defense deployments

Docker Compose installation

# Download the installer
wget https://github.com/goharbor/harbor/releases/download/v2.14.3/harbor-offline-installer-v2.14.3.tgz
tar xzf harbor-offline-installer-v2.14.3.tgz
cd harbor

# Copy and edit the config
cp harbor.yml.tmpl harbor.yml
# Edit harbor.yml: hostname, TLS certs, admin password, storage backend

# Run the installer
./install.sh --with-trivy

# The installer generates docker-compose.yml and starts all services
# Verify:
docker compose ps

Key harbor.yml settings

# harbor.yml (critical settings)
hostname: registry.example.com

https:
  port: 443
  certificate: /data/cert/server.crt
  private_key: /data/cert/server.key

harbor_admin_password: ChangeMeImmediately

database:
  password: db-password-here
  max_idle_conns: 100
  max_open_conns: 900

data_volume: /data    # local storage path

storage_service:
  s3:
    accesskey: AKIAIOSFODNN7EXAMPLE
    secretkey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    region: us-east-1
    bucket: harbor-registry
    rootdirectory: /registry

trivy:
  ignore_unfixed: false
  skip_update: false       # set true for air-gapped
  insecure: false

Helm chart deployment

# Add the Harbor Helm repo
helm repo add harbor https://helm.goharbor.io
helm repo update

# Install with custom values
helm install harbor harbor/harbor \
  --namespace harbor --create-namespace \
  --set expose.type=ingress \
  --set expose.ingress.hosts.core=registry.example.com \
  --set expose.ingress.className=nginx \
  --set externalURL=https://registry.example.com \
  --set persistence.persistentVolumeClaim.registry.storageClass=gp3 \
  --set persistence.persistentVolumeClaim.registry.size=500Gi \
  --set database.type=external \
  --set database.external.host=harbor-pg.rds.amazonaws.com \
  --set database.external.port=5432 \
  --set database.external.username=harbor \
  --set database.external.password=secret \
  --set redis.type=external \
  --set redis.external.addr=harbor-redis.cache.amazonaws.com:6379 \
  --values harbor-values.yaml
Recommendation

For production, always use external PostgreSQL and Redis. The built-in instances are single-container, no replication, no backups. Use RDS/Cloud SQL for PostgreSQL and ElastiCache/Memorystore for Redis. If on-prem, use Patroni for PostgreSQL HA and Redis Sentinel. The stateless Harbor components can be rebuilt in minutes; losing the database means losing all metadata, RBAC rules, scan results, and audit logs.

04

High Availability

Harbor does not have built-in clustering. HA is achieved by scaling stateless components horizontally and using HA-capable external services for stateful components. This is a common source of confusion — there is no "Harbor cluster mode" you can toggle on.

HA architecture

+-------------------+ | Load Balancer | | (TLS termination)| +---------+---------+ | +---------------+---------------+ | | | +-----v-----+ +-----v-----+ +-----v-----+ | Harbor | | Harbor | | Harbor | | Node 1 | | Node 2 | | Node 3 | | (core, | | (core, | | (core, | | portal, | | portal, | | portal, | | registry, | | registry, | | registry, | | jobsvc) | | jobsvc) | | jobsvc) | +-----+------+ +-----+------+ +-----+------+ | | | +-------+-------+-------+-------+ | | +-------v-------+ +-----v-------+ | PostgreSQL | | Redis | | (Patroni or | | (Sentinel or | | managed DB) | | managed) | +-------+-------+ +--------------+ | +-------v-------+ | Shared Storage| | (S3 / GCS / | | NFS / Blob) | +---------------+

Stateless components (scale horizontally)

  • Core Service — Run 2-3 replicas behind a load balancer. Each instance is identical. Session state is in Redis.
  • Portal — Static web UI, scales trivially. Typically combined with Core in the same pod (Helm chart default).
  • Registry (Distribution) — Multiple replicas all pointing at the same storage backend. S3 or shared filesystem required.
  • Job Service — Runs async tasks. Multiple replicas consume from the same Redis queue. Jobs are idempotent.
  • Trivy — Stateless scanner. Multiple replicas handle parallel scan requests.

Stateful components (use external HA services)

  • PostgreSQL — Use managed databases (RDS, Cloud SQL, Azure DB) or Patroni for on-prem HA. Streaming replication with automatic failover.
  • Redis — Use managed Redis (ElastiCache, Memorystore) or Redis Sentinel for on-prem. Redis Cluster mode is not natively supported by Harbor due to underlying library constraints — use Sentinel mode for HA.
  • Storage Backend — S3 is strongly recommended for HA. It is the only storage option that is inherently distributed and fault-tolerant. Filesystem storage requires a shared NFS mount, which introduces its own SPOF unless you run an HA NFS service.
Common Mistake

Running multiple Harbor instances with local filesystem storage and no shared backend. Each instance writes to its own disk, so images pushed to Node 1 are invisible to Node 2. You end up with a split-brain registry. Always use S3 or shared storage when running multiple Harbor instances.

Load balancer configuration

# Nginx LB example for Harbor HA
upstream harbor_backend {
    server harbor-node1:443;
    server harbor-node2:443;
    server harbor-node3:443;
}

server {
    listen 443 ssl;
    server_name registry.example.com;

    ssl_certificate     /etc/ssl/certs/registry.crt;
    ssl_certificate_key /etc/ssl/private/registry.key;

    # Large body size for image pushes
    client_max_body_size 0;

    # Chunked transfer encoding for large layers
    chunked_transfer_encoding on;

    location / {
        proxy_pass https://harbor_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_request_buffering off;
    }
}
Docker Layer Uploads

Image layer uploads can be multi-gigabyte. Ensure your load balancer has client_max_body_size 0 (unlimited) and reasonable timeouts. Proxy buffering should be disabled for streaming uploads. A 30-second timeout will kill large layer pushes.

05

Storage Backends

Harbor's registry component uses the Docker Distribution storage driver system. The storage backend holds image layers (blobs) and manifests. Choosing the right backend is one of the most impactful decisions for a Harbor deployment.

Recommended S3-Compatible

  • AWS S3, MinIO, Ceph RADOS Gateway, Wasabi
  • Inherently distributed, fault-tolerant, scalable
  • Best option for HA and multi-instance deployments
  • Supports lifecycle policies for cost optimization
  • Egress costs can add up with frequent pulls

Cloud GCS / Azure Blob

  • Native storage driver support in Distribution
  • Same benefits as S3 within respective cloud
  • Tight IAM integration (workload identity, managed identity)
  • Good choice when Harbor runs in the same cloud

Simple Filesystem

  • Local disk or NFS mount
  • Simplest to set up, no external dependencies
  • Cannot be shared across instances without NFS
  • NFS introduces latency and single point of failure
  • Suitable for single-node or dev deployments

Legacy Swift

  • OpenStack Swift object storage
  • Relevant for OpenStack-based private clouds
  • Less common in modern deployments
  • Full storage driver support in Distribution

S3 storage configuration

# harbor.yml storage configuration for S3
storage_service:
  s3:
    accesskey: AKIAIOSFODNN7EXAMPLE
    secretkey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    region: us-east-1
    bucket: harbor-registry
    regionendpoint: https://s3.us-east-1.amazonaws.com  # required for MinIO
    rootdirectory: /v2                                   # prefix in bucket
    encrypt: true                                        # SSE-S3 encryption
    multipartcopychunksize: 33554432                     # 32MB chunk for multipart
    multipartcopymaxconcurrency: 100
    multipartcopythresholdsize: 33554432

Garbage collection

Deleting an image tag in Harbor performs a soft delete — the tag is removed from the metadata database, but the blobs remain in storage. To reclaim disk space, you must run garbage collection (GC).

# Docker Compose: run GC manually
docker compose exec registryctl /home/harbor/harbor_registryctl garbage-collect

# Or trigger via API
curl -X POST "https://registry.example.com/api/v2.0/system/gc/schedule" \
  -H "Content-Type: application/json" \
  -u "admin:password" \
  -d '{"schedule":{"type":"Manual"}}'

# Schedule automatic GC (e.g., weekly at 2 AM Saturday)
curl -X PUT "https://registry.example.com/api/v2.0/system/gc/schedule" \
  -H "Content-Type: application/json" \
  -u "admin:password" \
  -d '{"schedule":{"type":"Weekly","cron":"0 0 2 * * 6"}}'
GC and Uptime

Since Harbor v2.1, garbage collection is non-blocking — you can push, pull, and delete artifacts while GC runs in the background without placing the registry in read-only mode. For large registries (multi-TB), GC can still take hours. You can configure the number of parallel GC workers to control resource usage. Despite being non-blocking, it is still good practice to schedule GC during off-peak hours to minimize resource contention.

Cost optimization for large registries

  • Tag retention policies — Automatically delete old tags. Keep the last N tags, or tags newer than N days. Combined with GC, this keeps storage manageable.
  • S3 Intelligent-Tiering — Image layers that haven't been pulled in months move to cheaper storage automatically.
  • Deduplication — Docker Distribution already deduplicates blobs (layers shared across images are stored once). This is automatic and significant — base images shared across hundreds of apps are stored once.
  • Monitor storage growth — Track the harbor_project_quota_usage_bytes metric. Set alerts when total storage exceeds thresholds.
06

Replication

Harbor supports replicating images between Harbor instances and between Harbor and other registries (Docker Hub, GCR, ECR, ACR, Quay, GitLab, etc.). Replication is a key feature for multi-datacenter deployments, disaster recovery, and hybrid cloud architectures.

Replication modes

Push-Based

The source Harbor instance pushes images to the destination. Use this when the source is in a trusted zone and can reach the destination.

  • Source initiates the transfer
  • Good for CI/CD pipelines — push to staging, replicate to production
  • Event-driven: trigger on push, or scheduled

Pull-Based

The destination Harbor instance pulls images from the source. Use this when the destination is in a restricted zone that cannot accept inbound connections.

  • Destination initiates the transfer
  • Good for air-gapped or edge sites that periodically sync
  • Scheduled: cron-based sync intervals

Replication policies

Policies define what gets replicated, with fine-grained filters:

  • By project — Replicate all images in specific projects
  • By repository — Filter by repository name pattern (e.g., myproject/api-*)
  • By tag — Filter by tag pattern (e.g., v* for release tags only, exclude latest)
  • By label — Replicate only images with specific labels (e.g., approved-for-production)
  • By resource type — Images, Helm charts, or both
# Create a replication policy via API
curl -X POST "https://registry.example.com/api/v2.0/replication/policies" \
  -H "Content-Type: application/json" \
  -u "admin:password" \
  -d '{
    "name": "prod-sync",
    "src_registry": {"id": 1},
    "dest_namespace": "production",
    "trigger": {
      "type": "event_based"
    },
    "filters": [
      {"type": "name", "value": "myproject/**"},
      {"type": "tag", "value": "v*"}
    ],
    "enabled": true,
    "override": true
  }'

Cross-datacenter DR pattern

A common production pattern for disaster recovery:

  1. Primary site (DC1) — Harbor with S3 storage, receives all pushes from CI/CD
  2. DR site (DC2) — Harbor with its own S3 storage, receives push-based replication from DC1
  3. Replication policy: event-driven, replicate all projects, all tags
  4. DNS failover: if DC1 is down, switch registry.example.com to DC2
  5. RTO depends on DNS TTL and replication lag (typically minutes for event-driven replication)
Bandwidth

Container images are large. A single image can be 500 MB - 2 GB. Replicating hundreds of images across datacenters saturates WAN links. Use tag filters to replicate only what's needed at the destination, not everything. Consider scheduling bulk replication during off-peak hours and using event-based replication only for critical images.

07

Security

Security is Harbor's strongest differentiator over a bare Docker Distribution registry. The combination of vulnerability scanning, image signing, RBAC, and policy enforcement makes Harbor a supply chain security tool, not just a registry.

Scanning Trivy Integration

  • Scan images automatically on push (scan-on-push)
  • Manual scan via UI or API
  • Severity levels: Critical, High, Medium, Low, Negligible
  • Block pulls of images exceeding a severity threshold
  • CVE allowlisting for accepted vulnerabilities

Signing Content Trust

  • Cosign: keyless signing via Sigstore (Fulcio + Rekor)
  • Notary: TUF-based Docker Content Trust (removed in v2.9; legacy only)
  • Enforce "only signed images can be pulled" per project
  • Cosign signatures stored as OCI artifacts alongside the image

Access RBAC & Auth

  • Project-level roles: Admin, Maintainer (formerly Master), Developer, Guest, Limited Guest
  • Robot accounts for CI/CD (scoped to specific projects and actions)
  • OIDC integration (Keycloak, Azure AD, Okta, Dex)
  • LDAP/AD for enterprise user directories
  • Audit logs for all operations

Policy Enforcement

  • Immutable tags: prevent overwriting (e.g., v1.0.0 cannot be re-pushed)
  • Vulnerability prevention policy: block pulling images with critical CVEs
  • Tag retention policies: auto-delete old/untagged images
  • Network policies: restrict which pods can reach Harbor (K8s)

Scan-on-push configuration

# Enable scan-on-push for a project via API
curl -X PUT "https://registry.example.com/api/v2.0/projects/myproject" \
  -H "Content-Type: application/json" \
  -u "admin:password" \
  -d '{"metadata": {"auto_scan": "true"}}'

# Set a vulnerability prevention policy (block critical CVEs)
curl -X PUT "https://registry.example.com/api/v2.0/projects/myproject" \
  -H "Content-Type: application/json" \
  -u "admin:password" \
  -d '{"metadata": {"prevent_vul": "true", "severity": "critical"}}'

Robot accounts for CI/CD

# Create a robot account scoped to a project
curl -X POST "https://registry.example.com/api/v2.0/robots" \
  -H "Content-Type: application/json" \
  -u "admin:password" \
  -d '{
    "name": "ci-pipeline",
    "duration": -1,
    "level": "project",
    "permissions": [
      {
        "namespace": "myproject",
        "kind": "project",
        "access": [
          {"resource": "repository", "action": "push"},
          {"resource": "repository", "action": "pull"},
          {"resource": "tag", "action": "create"}
        ]
      }
    ]
  }'
# Response includes the robot token -- store it securely!

Image signing with Cosign

# Sign an image with Cosign (keyless, via Sigstore)
cosign sign --yes registry.example.com/myproject/myapp:v1.2

# Sign with a private key
cosign sign --key cosign.key registry.example.com/myproject/myapp:v1.2

# Verify a signature
cosign verify --key cosign.pub registry.example.com/myproject/myapp:v1.2

# Verify keyless signature (checks Fulcio cert + Rekor transparency log)
cosign verify \
  --certificate-identity=ci@example.com \
  --certificate-oidc-issuer=https://accounts.google.com \
  registry.example.com/myproject/myapp:v1.2

TLS configuration

Harbor must run with TLS in production. Docker and Podman refuse to push/pull from non-HTTPS registries unless explicitly configured as insecure (which you should never do in production).

# Generate a certificate (Let's Encrypt recommended for public-facing)
# For internal registries, use your organization's CA
openssl req -x509 -nodes -days 365 \
  -newkey rsa:4096 \
  -keyout /data/cert/server.key \
  -out /data/cert/server.crt \
  -subj "/CN=registry.example.com" \
  -addext "subjectAltName=DNS:registry.example.com"

# Distribute the CA cert to all Docker/Podman clients
# Docker:
mkdir -p /etc/docker/certs.d/registry.example.com/
cp ca.crt /etc/docker/certs.d/registry.example.com/ca.crt

# Podman:
cp ca.crt /etc/containers/certs.d/registry.example.com/ca.crt
Supply Chain Security Stack

The full Harbor security stack for a production deployment: TLS everywhere + OIDC authentication + project-level RBAC + robot accounts for CI/CD + scan-on-push with Trivy + vulnerability prevention policy (block critical CVEs from being pulled) + Cosign image signing (keyless via Sigstore in CI/CD) + immutable tags for release versions + Kyverno or OPA Gatekeeper for admission control. This gives you end-to-end provenance and enforcement.

08

Image Management

Harbor organizes images into projects, which are the fundamental unit of access control and policy enforcement. Understanding the hierarchy and management features is essential for keeping a registry healthy at scale.

Project hierarchy

  • Project — Top-level container (e.g., myproject). Maps to the first path segment in the image name: registry.example.com/myproject/myapp:v1
  • Repository — An image name within a project (e.g., myproject/myapp). Contains multiple tags.
  • Tag — A specific version of an image (e.g., v1.2.3, latest, sha-abc123)
  • Label — User-defined metadata attached to images for filtering, replication, and organization

Tag retention policies

Without retention policies, registries grow unbounded. Every CI/CD pipeline push adds a new tag. Retention policies automatically clean up old tags.

# Create a tag retention policy via API
# Keep the 10 most recent tags matching "v*" and always keep "latest"
curl -X POST "https://registry.example.com/api/v2.0/retentions" \
  -H "Content-Type: application/json" \
  -u "admin:password" \
  -d '{
    "algorithm": "or",
    "rules": [
      {
        "action": "retain",
        "template": "latestPushedK",
        "params": {"latestPushedK": 10},
        "tag_selectors": [{"kind": "doublestar", "decoration": "matches", "pattern": "v*"}],
        "scope_selectors": {"repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}]}
      },
      {
        "action": "retain",
        "template": "always",
        "tag_selectors": [{"kind": "doublestar", "decoration": "matches", "pattern": "latest"}],
        "scope_selectors": {"repository": [{"kind": "doublestar", "decoration": "repoMatches", "pattern": "**"}]}
      }
    ],
    "trigger": {"kind": "Schedule", "settings": {"cron": "0 0 3 * * *"}}
  }'

Proxy cache (pull-through cache)

Harbor can act as a pull-through cache for remote registries. When a client pulls registry.example.com/dockerhub-cache/library/nginx:latest, Harbor checks its local cache first. If the image is not cached or is stale, Harbor pulls it from Docker Hub, caches it locally, and serves it to the client.

  • Reduces Docker Hub rate limiting — Unauthenticated pulls are limited to 100 per 6 hours, Docker Personal accounts to 200 per 6 hours (paid subscribers get unlimited pulls). A proxy cache means only the first pull hits Docker Hub.
  • Bandwidth savings — Images are pulled once from the internet, then served locally at LAN speed.
  • Resilience — If Docker Hub is down, cached images are still available.
  • Supports: Docker Hub, GCR, Quay, GitHub Container Registry, ECR, and any OCI-compliant registry.
# Create a proxy cache project for Docker Hub
# In the Harbor UI: Projects > New Project > Proxy Cache
# Or via API:
curl -X POST "https://registry.example.com/api/v2.0/projects" \
  -H "Content-Type: application/json" \
  -u "admin:password" \
  -d '{
    "project_name": "dockerhub-cache",
    "registry_id": 1,
    "metadata": {"public": "true"}
  }'

# Pull through the cache (from any Docker client):
docker pull registry.example.com/dockerhub-cache/library/nginx:1.25

Quota management

Projects can have storage quotas to prevent any single team or project from consuming all available storage:

# Set a 50 GB quota on a project
curl -X PUT "https://registry.example.com/api/v2.0/projects/myproject" \
  -H "Content-Type: application/json" \
  -u "admin:password" \
  -d '{"storage_limit": 53687091200}'  # 50 GB in bytes

Multi-architecture images

Harbor fully supports OCI image indexes (multi-arch manifests). Push multi-platform images with docker buildx or crane, and Harbor stores and serves the correct architecture automatically:

# Build and push a multi-arch image
docker buildx create --use
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag registry.example.com/myproject/myapp:v1.2 \
  --push .
Immutable Tags

Enable immutable tags on production projects. Once v1.2.3 is pushed, it cannot be overwritten. This prevents accidental or malicious overwrites of release artifacts. CI/CD pipelines should use unique tags (git SHA, build number) and only tag specific versions as immutable releases. The latest tag should not be immutable — it needs to float.

09

Upgrades

Harbor upgrades involve database migrations and container image updates. The upgrade process differs between Docker Compose and Helm deployments, but both require careful planning.

Pre-upgrade checklist

  1. Read the release notes — Check for breaking changes, deprecated features, and migration notes at github.com/goharbor/harbor/releases
  2. Back up the databasepg_dump the entire Harbor database. This is non-negotiable.
  3. Back up the configurationharbor.yml, TLS certificates, custom configs
  4. Test in staging first — Restore the database backup to a staging instance and upgrade there
  5. Check the upgrade path — Harbor supports upgrading from N-2 to N (two previous minor versions). Skipping more than two minor versions (e.g., 2.10 to 2.14) requires stepping through intermediate versions.

Docker Compose upgrade

# 1. Back up the database
docker compose exec harbor-db pg_dump -U postgres registry > harbor-backup-$(date +%Y%m%d).sql

# 2. Back up the config
cp harbor.yml harbor.yml.bak
cp -r /data/secret /data/secret.bak

# 3. Stop Harbor
docker compose down

# 4. Download the new version
wget https://github.com/goharbor/harbor/releases/download/v2.14.3/harbor-offline-installer-v2.14.3.tgz
tar xzf harbor-offline-installer-v2.14.3.tgz -C /opt/harbor-new

# 5. Copy your existing harbor.yml to the new directory
cp harbor.yml /opt/harbor-new/harbor/harbor.yml

# 6. Run the migration tool (if required by release notes)
docker run -it --rm \
  -v /data/database:/var/lib/postgresql/data \
  goharbor/harbor-db-migrator:v2.14.3 up

# 7. Run the installer
cd /opt/harbor-new/harbor
./install.sh --with-trivy

# 8. Verify
docker compose ps
curl -s https://registry.example.com/api/v2.0/systeminfo | jq .harbor_version

Helm chart upgrade

# 1. Back up the external database
pg_dump -h harbor-pg.rds.amazonaws.com -U harbor -d registry > harbor-backup.sql

# 2. Update the Helm repo
helm repo update harbor

# 3. Check what will change
helm diff upgrade harbor harbor/harbor \
  --namespace harbor \
  --values harbor-values.yaml

# 4. Perform the upgrade
helm upgrade harbor harbor/harbor \
  --namespace harbor \
  --values harbor-values.yaml \
  --timeout 10m

# 5. Watch the rollout
kubectl -n harbor rollout status deployment/harbor-core
kubectl -n harbor get pods
Database Migrations

Harbor database migrations are not reversible. If an upgrade fails after the migration runs, you cannot simply roll back to the previous Harbor version — the database schema has changed. Always have a database backup you have tested restoring. "We backed it up" is not the same as "we verified we can restore it."

Upgrade path rules

ScenarioSupported?Notes
2.13.x to 2.14.xYesN-1 minor version upgrade, straightforward
2.12.x to 2.14.xYesN-2 minor version upgrade, supported directly
2.14.0 to 2.14.3YesPatch upgrades are safe, no DB migration usually
2.10.x to 2.14.xNo (step required)Exceeds N-2 range. Step through intermediate versions (e.g., 2.10 → 2.12 → 2.14)
1.x to 2.xComplexMajor version jump with significant schema changes. Follow the official migration guide carefully
Rolling Upgrades on K8s

Helm upgrades on Kubernetes perform a rolling update of deployments. During the rollout, old and new pods coexist. Ensure all replicas are running the same Harbor version before declaring the upgrade complete. If the new pods fail health checks, Kubernetes will automatically roll back the deployment — but the database migration may have already run. This is why database backup is critical even on K8s.

10

Monitoring

Harbor exposes Prometheus-compatible metrics and health check endpoints. Production deployments need proactive monitoring to catch storage issues, scan backlogs, and authentication failures before they become incidents.

Key metrics

MetricAlert ThresholdWhy
harbor_project_quota_usage_bytes> 80% of quotaProject approaching quota limit, pushes will fail
harbor_task_queue_size> 100 pendingScan or replication backlog growing — job service may be overwhelmed
harbor_artifact_pulledBaseline deviationUnusual pull patterns may indicate security events or outages
harbor_artifact_pushedBaseline deviationSudden spike may indicate CI/CD flood or misconfigured pipeline
Storage backend usage> 80% capacityRunning out of storage is a registry-down event
Scan queue depth> 50 pendingTrivy scanner falling behind. May need more replicas or resources.
Database connections> 80% of maxConnection exhaustion causes Harbor to become unresponsive
Replication lag> 1 hourDR site is stale. Network or destination may be down.

Prometheus metrics endpoint

# Enable metrics in harbor.yml
metric:
  enabled: true
  type: prometheus
  port: 9090
  path: /metrics

# Prometheus scrape config
- job_name: 'harbor'
  scrape_interval: 30s
  metrics_path: /metrics
  static_configs:
    - targets:
      - harbor-core:9090
      - harbor-registry:9090
      - harbor-jobservice:9090
      - harbor-exporter:9090

Health check endpoints

# Overall health check
curl -s https://registry.example.com/api/v2.0/health | jq
# Returns component status for each service:
# {
#   "status": "healthy",
#   "components": [
#     {"name": "core", "status": "healthy"},
#     {"name": "database", "status": "healthy"},
#     {"name": "redis", "status": "healthy"},
#     {"name": "registry", "status": "healthy"},
#     {"name": "registryctl", "status": "healthy"},
#     {"name": "trivy", "status": "healthy"}
#   ]
# }

# Use in Kubernetes liveness/readiness probes
# The Helm chart configures these automatically

Grafana dashboard

Community Grafana dashboards are available for Harbor. Key panels to include:

  • Push/pull rate over time (operations per minute)
  • Storage usage per project (trend line with forecast)
  • Scan results distribution (pie chart: critical, high, medium, low)
  • Replication status (success/failure counts, lag time)
  • API response latency (p50, p95, p99)
  • Active robot accounts and their last-used timestamps

Log aggregation

# Docker Compose: logs go to /var/log/harbor/ by default
# Configure log rotation in harbor.yml:
log:
  level: info       # debug, info, warning, error, fatal
  local:
    rotate_count: 50
    rotate_size: 200M
    location: /var/log/harbor

# For K8s: use a log aggregator (Loki, ELK, Fluentd)
# Harbor pods write to stdout/stderr (standard K8s logging)
Minimum Monitoring

At minimum, monitor three things: storage usage (the number one cause of Harbor outages), health endpoint (any component going unhealthy), and database connection count (connection exhaustion makes Harbor unresponsive). Everything else is important but these three catch the most common production issues.

11

Licensing

Harbor is licensed under Apache 2.0 and is a CNCF Graduated project. There is no enterprise edition, no feature gating, no paid tier. Every feature is available in the open-source release. This is a significant differentiator in the registry space, where most competitors have commercial editions with gated features.

CNCF Graduated status

CNCF Graduation means Harbor has met the foundation's highest maturity bar:

  • Adopted by a diverse set of production users
  • Passed an independent security audit
  • Has a healthy contributor ecosystem (not single-vendor dominated)
  • Follows CNCF governance practices

Commercial support

While Harbor itself is free, commercial support is available:

  • VMware/Broadcom — Harbor was originally created by VMware. VMware Tanzu products include Harbor as a supported component. Following the Broadcom acquisition, the support landscape is evolving.
  • Community support — GitHub issues, CNCF Slack (#harbor channel), community mailing list. Response times vary.
  • Third-party consultants — Several CNCF-ecosystem consultancies offer Harbor deployment and support.

Registry comparison (licensing & cost)

RegistryLicenseSelf-HostedCost ModelEnterprise Features
HarborApache 2.0Yes (only)Free (infra only)All included
QuayApache 2.0Yes + managedFree OSS / RHEL sub for supportAll in OSS, support requires sub
JFrog ArtifactoryProprietaryYes + SaaS$150+/month (Pro tier)Many features gated to paid tiers
Sonatype NexusEPL (Community) / Proprietary (Pro)YesFree Community / paid ProDocker support available in Community Edition (formerly OSS)
Docker HubProprietaryNoFree + paid tiersPaid features (Scout, teams)
ECR / GCR / ACRProprietaryNoStorage + egressCloud-native integration
Recommendation

For most organizations, Harbor's zero-cost licensing is a major advantage. The total cost of ownership is just infrastructure (compute, storage, database). A production Harbor on Kubernetes with RDS and S3 typically costs $200-500/month depending on scale — compared to thousands per month for commercial registries at equivalent scale. The trade-off is operational responsibility: you run it, you maintain it.

12

Consultant's Checklist

Before deploying Harbor for a client, work through these questions:

  1. Deployment platform? — Kubernetes (Helm) or Docker Compose on VMs? K8s is strongly preferred for production.
  2. Storage backend? — S3/MinIO for HA and scalability. Filesystem only for single-node dev setups. Estimate initial storage: how many images, how large, how fast will it grow?
  3. Database? — External PostgreSQL with HA (RDS, Patroni). Never use the built-in database for production.
  4. HA requirements? — Multiple Harbor replicas + external DB/Redis + S3 storage + load balancer. Or is single-node acceptable?
  5. Authentication? — OIDC (Keycloak, Azure AD, Okta) or LDAP? How will CI/CD authenticate (robot accounts)?
  6. Security scanning? — Scan-on-push enabled? Vulnerability prevention policy (block critical CVEs)? Air-gapped Trivy DB?
  7. Image signing? — Cosign (recommended; Notary v1 was removed in Harbor v2.9). Keyless (Sigstore) or key-based? Verification policies in the deployment pipeline (Kyverno, OPA Gatekeeper)?
  8. Replication? — Multi-site deployment? DR requirements? Push or pull mode? Bandwidth between sites?
  9. Proxy cache? — Cache Docker Hub pulls to avoid rate limiting? Cache other upstream registries?
  10. Retention and GC? — Tag retention policies per project. GC schedule. Maintenance window for GC.
  11. TLS certificates? — Let's Encrypt, internal CA, or manual? Certificate distribution to all Docker/K8s clients.
  12. Monitoring? — Prometheus metrics, health checks, storage alerts, scan backlog alerts. Grafana dashboard.
  13. Backup strategy? — Database backups (pg_dump + WAL archiving). Config backups. S3 bucket versioning for registry blobs.
  14. Upgrade plan? — Who owns upgrades? What's the upgrade cadence? Staging environment for testing upgrades?
Sizing Guide

Small (< 100 images, < 10 users): Single node, Docker Compose, filesystem storage, built-in DB. 4 vCPU, 8 GB RAM, 100 GB disk.
Medium (100-1000 images, 10-100 users): 2-3 replicas on K8s, external PostgreSQL, S3 storage. 8 vCPU total, 16 GB RAM total, S3 unlimited.
Large (> 1000 images, > 100 users): 3+ replicas, RDS Multi-AZ, ElastiCache, S3 with lifecycle policies. 16+ vCPU total, 32+ GB RAM total.