OpenMCF logoOpenMCF

Loading...

Kubernetes Harbor

Deploys Harbor cloud-native container registry on Kubernetes using the official Harbor Helm chart. Provisions separate Harbor Core, Portal, Registry, and Jobservice components with independent resource tuning. Supports self-managed or external PostgreSQL and Redis, multiple artifact storage backends (S3, GCS, Azure Blob, Alibaba OSS, filesystem), arbitrary Helm value overrides, and optional external access through Istio Gateway API ingress with TLS termination.

What Gets Created

When you deploy a KubernetesHarbor resource, OpenMCF provisions:

  • Kubernetes Namespace — created if createNamespace is true
  • Harbor Helm Release — the official harbor chart from https://helm.goharbor.io, which creates:
    • Harbor Core pod (API server, authentication, webhook) on port 80
    • Harbor Portal pod (web UI) on port 80
    • Harbor Registry pod (Docker/OCI registry backend) on port 5000
    • Harbor Jobservice pod (background job execution)
    • Kubernetes Services for each component for cluster-internal access
  • PostgreSQL — either a self-managed in-cluster instance with persistent storage, or integration with an external PostgreSQL endpoint
  • Redis — either a self-managed in-cluster instance, or integration with an external Redis endpoint (including Sentinel support)
  • Artifact Storage — configured backend for container images and Helm charts (filesystem PVC, S3, GCS, Azure Blob, or Alibaba OSS)
  • Admin Credentials — a Kubernetes Secret containing the Harbor admin password
  • Ingress Resources (when ingress.core.enabled is true):
    • cert-manager Certificate for TLS, issued by a ClusterIssuer matching the ingress domain
    • Gateway API Gateway with HTTPS listener (port 443) and TLS termination
    • HTTPRoute for HTTPS traffic forwarding to the Harbor Core service

Prerequisites

  • A Kubernetes cluster with kubectl configured for access
  • Istio ingress gateway installed (only if using ingress)
  • cert-manager with a ClusterIssuer matching your ingress domain (only if using ingress)
  • Gateway API CRDs installed in the cluster (only if using ingress)
  • An S3-compatible bucket, GCS bucket, or Azure Blob container provisioned (only if using external object storage instead of filesystem)

Quick Start

Create a file harbor.yaml:

apiVersion: kubernetes.openmcf.org/v1
kind: KubernetesHarbor
metadata:
  name: my-harbor
  labels:
    openmcf.org/provisioner: pulumi
    pulumi.openmcf.org/organization: my-org
    pulumi.openmcf.org/project: my-project
    pulumi.openmcf.org/stack.name: dev.KubernetesHarbor.my-harbor
spec:
  namespace:
    value: harbor-dev
  createNamespace: true
  database:
    isExternal: false
  cache:
    isExternal: false
  storage:
    type: filesystem
    filesystem:
      diskSize: "100Gi"

Deploy:

openmcf apply -f harbor.yaml

This creates a Harbor instance with self-managed PostgreSQL (20Gi disk) and Redis (8Gi disk), filesystem-based artifact storage, and default resource allocations for all components. An admin user is created automatically with a generated password stored in a Kubernetes Secret.

Configuration Reference

Required Fields

FieldTypeDescriptionValidation
namespaceStringValueOrRefKubernetes namespace for the Harbor deployment. Use value for a direct string or valueFrom to reference a KubernetesNamespace resource.Required
databaseobjectPostgreSQL configuration. Set isExternal: false for self-managed or isExternal: true with externalDatabase connection details.Required
cacheobjectRedis configuration. Set isExternal: false for self-managed or isExternal: true with externalCache connection details.Required
storageobjectArtifact storage backend. Must include type (filesystem, s3, gcs, azure, or oss) and the matching configuration block.Required; type-specific sub-object required

Optional Fields

FieldTypeDefaultDescription
createNamespaceboolfalseCreate the namespace if it does not exist.
coreContainer.replicasint321Number of Harbor Core pods.
coreContainer.resources.limits.cpustring"1000m"CPU limit for Harbor Core.
coreContainer.resources.limits.memorystring"2Gi"Memory limit for Harbor Core.
coreContainer.resources.requests.cpustring"200m"CPU request for Harbor Core.
coreContainer.resources.requests.memorystring"512Mi"Memory request for Harbor Core.
coreContainer.image.repostringchart defaultCustom container image repository for Harbor Core.
coreContainer.image.tagstringchart defaultCustom container image tag for Harbor Core.
portalContainer.replicasint321Number of Harbor Portal pods.
portalContainer.resources.limits.cpustring"500m"CPU limit for Harbor Portal.
portalContainer.resources.limits.memorystring"512Mi"Memory limit for Harbor Portal.
portalContainer.resources.requests.cpustring"100m"CPU request for Harbor Portal.
portalContainer.resources.requests.memorystring"256Mi"Memory request for Harbor Portal.
registryContainer.replicasint321Number of Harbor Registry pods.
registryContainer.resources.limits.cpustring"1000m"CPU limit for Harbor Registry.
registryContainer.resources.limits.memorystring"2Gi"Memory limit for Harbor Registry.
registryContainer.resources.requests.cpustring"200m"CPU request for Harbor Registry.
registryContainer.resources.requests.memorystring"512Mi"Memory request for Harbor Registry.
jobserviceContainer.replicasint321Number of Harbor Jobservice pods.
jobserviceContainer.resources.limits.cpustring"1000m"CPU limit for Harbor Jobservice.
jobserviceContainer.resources.limits.memorystring"1Gi"Memory limit for Harbor Jobservice.
jobserviceContainer.resources.requests.cpustring"100m"CPU request for Harbor Jobservice.
jobserviceContainer.resources.requests.memorystring"256Mi"Memory request for Harbor Jobservice.
database.managedDatabase.container.replicasint321Number of self-managed PostgreSQL pods.
database.managedDatabase.container.resources.limits.cpustring"1000m"CPU limit for self-managed PostgreSQL.
database.managedDatabase.container.resources.limits.memorystring"2Gi"Memory limit for self-managed PostgreSQL.
database.managedDatabase.container.resources.requests.cpustring"200m"CPU request for self-managed PostgreSQL.
database.managedDatabase.container.resources.requests.memorystring"512Mi"Memory request for self-managed PostgreSQL.
database.managedDatabase.container.persistenceEnabledbooltrueEnable persistent storage for PostgreSQL data.
database.managedDatabase.container.diskSizestring"20Gi"Persistent volume size for PostgreSQL. Cannot be changed after creation.
database.externalDatabase.hoststring--Hostname of the external PostgreSQL instance. Required when database.isExternal is true.
database.externalDatabase.portint325432Port for external PostgreSQL.
database.externalDatabase.usernamestring--Username for external PostgreSQL authentication.
database.externalDatabase.passwordstring--Password for external PostgreSQL authentication.
database.externalDatabase.coreDatabasestring"registry"Database name for Harbor Core.
database.externalDatabase.clairDatabasestring"clair"Database name for Clair vulnerability scanner.
database.externalDatabase.notaryServerDatabasestring"notary_server"Database name for Notary Server.
database.externalDatabase.notarySignerDatabasestring"notary_signer"Database name for Notary Signer.
database.externalDatabase.useSslboolfalseEnable SSL/TLS connection to external PostgreSQL.
cache.managedCache.container.replicasint321Number of self-managed Redis pods.
cache.managedCache.container.resources.limits.cpustring"500m"CPU limit for self-managed Redis.
cache.managedCache.container.resources.limits.memorystring"512Mi"Memory limit for self-managed Redis.
cache.managedCache.container.resources.requests.cpustring"100m"CPU request for self-managed Redis.
cache.managedCache.container.resources.requests.memorystring"256Mi"Memory request for self-managed Redis.
cache.managedCache.container.persistenceEnabledbooltrueEnable persistent storage for Redis data.
cache.managedCache.container.diskSizestring"8Gi"Persistent volume size for Redis.
cache.externalCache.hoststring--Hostname of the external Redis instance. Required when cache.isExternal is true.
cache.externalCache.portint326379Port for external Redis.
cache.externalCache.usernamestring--Username for external Redis (if ACLs are enabled).
cache.externalCache.passwordstring--Password for external Redis.
cache.externalCache.databaseIndexint320Redis database index.
cache.externalCache.useSentinelboolfalseEnable Redis Sentinel for high availability.
cache.externalCache.sentinelMasterSetstring--Sentinel master set name. Required when useSentinel is true.
storage.filesystem.diskSizestring--PVC size for filesystem storage (e.g., "100Gi"). Cannot be changed after creation.
storage.filesystem.storageClassstringcluster defaultKubernetes StorageClass for the PVC.
storage.s3.bucketstring--S3 bucket name. Required when storage.type is s3.
storage.s3.regionstring--AWS region for the S3 bucket.
storage.s3.accessKeystring--AWS access key ID.
storage.s3.secretKeystring--AWS secret access key.
storage.s3.endpointUrlstring--Custom endpoint URL for S3-compatible services (e.g., MinIO).
storage.s3.encryptboolfalseEnable server-side encryption.
storage.s3.secureboolfalseUse HTTPS connection to S3.
storage.gcs.bucketstring--GCS bucket name. Required when storage.type is gcs.
storage.gcs.keyDatastring--Base64-encoded GCP service account key JSON.
storage.gcs.chunkSizeint325242880Upload chunk size in bytes.
storage.azure.accountNamestring--Azure storage account name. Required when storage.type is azure.
storage.azure.accountKeystring--Azure storage account key.
storage.azure.containerstring--Azure Blob container name.
ingress.core.enabledboolfalseEnable external access to Harbor Core/Portal via Istio Gateway API with TLS.
ingress.core.hostnamestring--Full hostname for external Harbor access (e.g., harbor.example.com). Required when ingress.core.enabled is true.
ingress.notary.enabledboolfalseEnable external access to the Notary service for image signing.
ingress.notary.hostnamestring--Full hostname for external Notary access. Required when ingress.notary.enabled is true.
helmValuesmap<string, string>{}Additional Helm chart values for customization. See the Harbor Helm chart values for available options.

Examples

Development Instance with Filesystem Storage

A minimal Harbor deployment using self-managed PostgreSQL, Redis, and local filesystem storage for development:

apiVersion: kubernetes.openmcf.org/v1
kind: KubernetesHarbor
metadata:
  name: dev-harbor
  labels:
    openmcf.org/provisioner: pulumi
    pulumi.openmcf.org/organization: my-org
    pulumi.openmcf.org/project: my-project
    pulumi.openmcf.org/stack.name: dev.KubernetesHarbor.dev-harbor
spec:
  namespace:
    value: harbor-dev
  createNamespace: true
  coreContainer:
    replicas: 1
    resources:
      limits:
        cpu: "500m"
        memory: "1Gi"
      requests:
        cpu: "100m"
        memory: "256Mi"
  portalContainer:
    replicas: 1
    resources:
      limits:
        cpu: "250m"
        memory: "256Mi"
      requests:
        cpu: "50m"
        memory: "128Mi"
  registryContainer:
    replicas: 1
    resources:
      limits:
        cpu: "500m"
        memory: "1Gi"
      requests:
        cpu: "100m"
        memory: "256Mi"
  database:
    isExternal: false
    managedDatabase:
      container:
        persistenceEnabled: true
        diskSize: "10Gi"
  cache:
    isExternal: false
    managedCache:
      container:
        persistenceEnabled: true
        diskSize: "4Gi"
  storage:
    type: filesystem
    filesystem:
      diskSize: "50Gi"

Production with S3 Storage and External Database

A deployment using an external PostgreSQL database, self-managed Redis, S3 object storage for artifacts, and increased replicas:

apiVersion: kubernetes.openmcf.org/v1
kind: KubernetesHarbor
metadata:
  name: prod-harbor
  labels:
    openmcf.org/provisioner: pulumi
    pulumi.openmcf.org/organization: my-org
    pulumi.openmcf.org/project: my-project
    pulumi.openmcf.org/stack.name: prod.KubernetesHarbor.prod-harbor
spec:
  namespace:
    value: harbor-prod
  createNamespace: true
  coreContainer:
    replicas: 2
    resources:
      limits:
        cpu: "2000m"
        memory: "4Gi"
      requests:
        cpu: "500m"
        memory: "1Gi"
  portalContainer:
    replicas: 2
    resources:
      limits:
        cpu: "1000m"
        memory: "1Gi"
      requests:
        cpu: "200m"
        memory: "512Mi"
  registryContainer:
    replicas: 2
    resources:
      limits:
        cpu: "2000m"
        memory: "4Gi"
      requests:
        cpu: "500m"
        memory: "1Gi"
  jobserviceContainer:
    replicas: 2
    resources:
      limits:
        cpu: "2000m"
        memory: "2Gi"
      requests:
        cpu: "250m"
        memory: "512Mi"
  database:
    isExternal: true
    externalDatabase:
      host: harbor-db.us-east-1.rds.amazonaws.com
      port: 5432
      username: harbor_admin
      password: changeme-use-secret-manager
      coreDatabase: harbor_registry
      clairDatabase: harbor_clair
      notaryServerDatabase: harbor_notary_server
      notarySignerDatabase: harbor_notary_signer
      useSsl: true
  cache:
    isExternal: false
    managedCache:
      container:
        replicas: 1
        resources:
          limits:
            cpu: "1000m"
            memory: "1Gi"
          requests:
            cpu: "200m"
            memory: "512Mi"
        persistenceEnabled: true
        diskSize: "16Gi"
  storage:
    type: s3
    s3:
      bucket: harbor-artifacts-prod
      region: us-east-1
      accessKey: AKIAIOSFODNN7EXAMPLE
      secretKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
      encrypt: true
      secure: true

Full-Featured with Ingress and GCS Storage

External HTTPS access with TLS, GCS artifact storage, external Redis with Sentinel, and Helm value overrides for Trivy vulnerability scanning:

apiVersion: kubernetes.openmcf.org/v1
kind: KubernetesHarbor
metadata:
  name: main-harbor
  labels:
    openmcf.org/provisioner: pulumi
    pulumi.openmcf.org/organization: my-org
    pulumi.openmcf.org/project: my-project
    pulumi.openmcf.org/stack.name: prod.KubernetesHarbor.main-harbor
spec:
  namespace:
    value: harbor-production
  createNamespace: true
  coreContainer:
    replicas: 3
    resources:
      limits:
        cpu: "4000m"
        memory: "8Gi"
      requests:
        cpu: "1000m"
        memory: "2Gi"
  portalContainer:
    replicas: 2
  registryContainer:
    replicas: 3
    resources:
      limits:
        cpu: "4000m"
        memory: "8Gi"
      requests:
        cpu: "1000m"
        memory: "2Gi"
  jobserviceContainer:
    replicas: 2
    resources:
      limits:
        cpu: "2000m"
        memory: "4Gi"
      requests:
        cpu: "500m"
        memory: "1Gi"
  database:
    isExternal: true
    externalDatabase:
      host: harbor-postgres.internal.example.com
      port: 5432
      username: harbor
      password: changeme-use-secret-manager
      useSsl: true
  cache:
    isExternal: true
    externalCache:
      host: harbor-redis.internal.example.com
      port: 6379
      password: changeme-use-secret-manager
      useSentinel: true
      sentinelMasterSet: harbor-master
  storage:
    type: gcs
    gcs:
      bucket: harbor-artifacts-production
      keyData: ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIKfQ==
  ingress:
    core:
      enabled: true
      hostname: harbor.example.com
    notary:
      enabled: true
      hostname: notary.example.com
  helmValues:
    trivy.enabled: "true"
    trivy.ignoreUnfixed: "true"

Stack Outputs

After deployment, the following outputs are available in status.outputs:

OutputTypeDescription
namespacestringKubernetes namespace where Harbor was created
coreServicestringName of the Kubernetes Service for Harbor Core (e.g., my-harbor-harbor-core)
portalServicestringName of the Kubernetes Service for Harbor Portal (e.g., my-harbor-harbor-portal)
registryServicestringName of the Kubernetes Service for Harbor Registry (e.g., my-harbor-harbor-registry)
jobserviceServicestringName of the Kubernetes Service for Harbor Jobservice (e.g., my-harbor-harbor-jobservice)
portForwardCommandstringReady-to-run kubectl port-forward command to access Harbor Portal locally on port 80
internalCoreEndpointstringCluster-internal endpoint for Harbor Core (e.g., my-harbor-harbor-core.harbor-dev.svc.cluster.local:80)
internalRegistryEndpointstringCluster-internal endpoint for Harbor Registry (e.g., my-harbor-harbor-registry.harbor-dev.svc.cluster.local:5000)
externalHostnamestringExternal hostname when core ingress is enabled (e.g., harbor.example.com)
registryExternalHostnamestringExternal hostname for Docker registry access (docker login, pull, push)
notaryExternalHostnamestringExternal hostname for Notary image signing service when notary ingress is enabled
adminUsernamestringHarbor admin username (default: admin)
adminPasswordSecretKubernetesSecretKeyReference to the Kubernetes Secret containing the admin password (name = {resourceName}-harbor-core, key = HARBOR_ADMIN_PASSWORD)
databaseEndpointstringCluster-internal PostgreSQL endpoint. Only populated when using self-managed database.
databaseUsernamestringPostgreSQL username. Only populated when using self-managed database.
databasePasswordSecretKubernetesSecretKeyReference to the Kubernetes Secret containing the PostgreSQL password. Only populated when using self-managed database.
redisEndpointstringCluster-internal Redis endpoint. Only populated when using self-managed cache.
redisPasswordSecretKubernetesSecretKeyReference to the Kubernetes Secret containing the Redis password. Only populated when using self-managed cache.

Related Components

  • KubernetesNamespace — pre-create a namespace to reference via valueFrom
  • KubernetesPostgres — deploy a standalone PostgreSQL instance as an external database for Harbor
  • KubernetesSecret — manage secrets for external database or storage credentials
  • KubernetesHelmRelease — deploy additional Helm charts alongside Harbor (e.g., monitoring exporters)

Next article

Kubernetes Helm Release

Kubernetes Helm Release Deploys any Helm chart to a Kubernetes cluster through OpenMCF's lifecycle management, acting as a generic escape hatch for workloads that are already packaged as Helm charts but do not have a dedicated OpenMCF component. The module handles chart fetching, namespace creation, value overrides, and the full apply/update/destroy lifecycle automatically. What Gets Created When you deploy a KubernetesHelmRelease resource, OpenMCF provisions: Namespace — created only when...
Read next article
Presets
2 ready-to-deploy configurationsView presets →