A modern, production-ready Helm chart for Harbor - the cloud native registry for Kubernetes.
This chart supports the following Harbor distributions:
- Harbor - The open-source cloud native registry
- Harbor-Next - Enhanced Harbor with additional enterprise features
- 8gears Container Registry - Fully managed Harbor service
helm install my-harbor oci://registry.goharbor.io/harbor-next/charts/harbor \
--set externalURL=https://harbor.example.com \
--set database.host=my-postgres.example.com \
--set database.password=secretThis chart is a ground-up redesign of the Harbor Helm chart with modern Kubernetes best practices:
| Feature | Legacy harbor-helm | This Chart |
|---|---|---|
| Configuration | 70+ helper templates, hardcoded env vars | toEnvVars pattern - any config works |
| Database | Built-in PostgreSQL StatefulSet | External only (production best practice) |
| Redis | Built-in Redis Deployment | Valkey subchart or external |
| Ingress | nginx reverse proxy + Ingress | Direct Ingress/Gateway API |
| Security | Basic security context | PSS Restricted profile compliant |
| Validation | None | JSON Schema validation |
| Resource Defaults | None (comments only) | Sensible defaults for all components |
| PodDisruptionBudget | Not available | Per-component PDB support |
| Templates | 48 files, 607-line helpers | ~28 files, 443-line helpers |
| values.yaml | 1,116 lines | ~700 lines |
The chart uses a unique toEnvVars pattern that converts nested YAML configuration to flat environment variables. This means any Harbor configuration option works without chart updates:
core:
config:
# These become CONFIG_KEY and NESTED_VALUE env vars
config_key: "value"
nested:
value: "something"
secret:
# These become secrets (base64 encoded)
sensitive_data: "secret-value"All containers run with Pod Security Standards (PSS) Restricted profile:
runAsNonRoot: true- No root containersreadOnlyRootFilesystem: true- Immutable container filesystemallowPrivilegeEscalation: false- No privilege escalationcapabilities.drop: ["ALL"]- No Linux capabilitiesseccompProfile.type: RuntimeDefault- Seccomp filtering enabled
- PodDisruptionBudgets - Ensure availability during node maintenance
- Resource requests/limits - Guaranteed QoS with sensible defaults
- Affinity/Anti-affinity - Control pod placement
- Multiple replicas - Scale any component horizontally
- Standard Kubernetes Ingress (default)
- Gateway API HTTPRoute (modern alternative)
- extraManifests for custom routing (Traefik IngressRoute, etc.)
Built-in values.schema.json provides:
- IDE autocompletion and validation
- Required field enforcement (
externalURL,database.host) - Type checking and enum validation
- Immediate feedback on configuration errors
┌─────────────────────────────────────────────────────────────┐
│ Ingress / Gateway API │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│ Portal │ │ Core │◄────────►│ Registry │
│ (UI) │ │ (API) │ │ (Images) │
└─────────┘ └────┬─────┘ └────┬─────┘
│ │
┌────────┴────────┐ │
▼ ▼ │
┌───────────┐ ┌──────────┐ │
│Jobservice │ │ Exporter │ │
│ (Tasks) │ │(Metrics) │ │
└─────┬─────┘ └──────────┘ │
│ │
┌───────────┴───────────┬─────────────────┘
▼ ▼
┌─────────┐ ┌──────────┐
│ Valkey │ │ Storage │
│ (Redis) │ │(PVC/S3/..)│
└─────────┘ └──────────┘
│
▼
┌──────────────┐
│ PostgreSQL │ (External - required)
└──────────────┘
- Kubernetes 1.33+ (we follow endoflife.date/kubernetes for supported versions)
- Helm 3.x
- External PostgreSQL database (required)
- PV provisioner (for filesystem storage)
helm install my-harbor oci://registry.goharbor.io/harbor-next/charts/harbor \
--namespace harbor \
--create-namespace \
--set externalURL=https://harbor.example.com \
--set database.host=postgres.example.com \
--set database.password=your-passwordhelm install my-harbor oci://registry.goharbor.io/harbor-next/charts/harbor \
--namespace harbor \
--create-namespace \
-f values-production.yamlhelm uninstall my-harbor --namespace harborNote: PersistentVolumeClaims are not deleted automatically. Remove them manually if needed.
| Repository | Name | Version |
|---|---|---|
| https://valkey.io/valkey-helm/ | valkey | 0.9.x |
| Key | Description |
|---|---|
externalURL |
Public URL for Harbor (e.g., https://harbor.example.com) |
database.host |
PostgreSQL host |
database.password |
PostgreSQL password (or use database.existingSecret) |
Each Harbor component (core, portal, registry, jobservice, exporter) supports:
| Key | Description |
|---|---|
<component>.replicas |
Number of replicas |
<component>.resources |
CPU/memory requests and limits |
<component>.config |
Application config (becomes ConfigMap env vars) |
<component>.secret |
Sensitive config (becomes Secret env vars) |
<component>.extraEnv |
Additional env vars with valueFrom support |
<component>.pdb.enabled |
Enable PodDisruptionBudget |
<component>.pdb.minAvailable |
Minimum available pods during disruption |
<component>.affinity |
Pod affinity rules |
<component>.nodeSelector |
Node selection constraints |
<component>.tolerations |
Pod tolerations |
<component>.securityContext |
Container security context |
<component>.podSecurityContext |
Pod security context |
<component>.serviceAccount.create |
Create dedicated ServiceAccount |
| Component | CPU Request | Memory Request | Memory Limit |
|---|---|---|---|
| Core | 100m | 256Mi | 512Mi |
| Registry | 100m | 256Mi | 512Mi |
| Portal | 100m | 128Mi | 256Mi |
| Jobservice | 100m | 256Mi | 512Mi |
| Exporter | 100m | 128Mi | 256Mi |
externalURL: https://harbor.example.com
# External database (required)
database:
host: postgres.example.com
password: your-db-password
sslmode: require
# HA: Multiple replicas with PDB
core:
replicas: 2
pdb:
enabled: true
minAvailable: 1
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
memory: 1Gi
portal:
replicas: 2
pdb:
enabled: true
minAvailable: 1
registry:
replicas: 2
pdb:
enabled: true
minAvailable: 1
# Ingress with TLS
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: harbor.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: harbor-tls
hosts:
- harbor.example.com
# Use Valkey for Redis
valkey:
enabled: true
architecture: standaloneregistry:
storage:
type: s3
s3:
region: us-east-1
bucket: my-harbor-bucket
accesskey: AKIAIOSFODNN7EXAMPLE
secretkey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Optional settings
regionendpoint: https://s3.us-east-1.amazonaws.com
encrypt: true
secure: true
persistence:
enabled: false # Disable PVC when using S3registry:
storage:
type: azure
azure:
accountname: mystorageaccount
accountkey: base64-encoded-key
container: harbor
persistence:
enabled: falseregistry:
storage:
type: gcs
gcs:
bucket: my-harbor-bucket
keyfile: |
{
"type": "service_account",
...
}
persistence:
enabled: falseingress:
enabled: false
gateway:
enabled: true
parentRefs:
- name: my-gateway
namespace: default
hostnames:
- harbor.example.comvalkey:
enabled: false
externalRedis:
host: redis.example.com
port: 6379
password: redis-password
# For Redis Sentinel:
# sentinelMasterSet: mymastertls:
certManager:
enabled: true
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
duration: 2160h # 90 days
renewBefore: 360h # 15 dayscore:
config:
# Any Harbor Core config option
token_expiration: 30
robot_token_duration: 30
# Nested config becomes NESTED_KEY_HERE env var
nested:
key_here: value
secret:
# Sensitive values (stored in Secret)
csrf_key: "your-csrf-key"
jobservice:
config:
max_job_workers: 20
job_loggers: "file,stdout"metrics:
serviceMonitor:
enabled: true
namespace: monitoring # Optional, defaults to release namespace
interval: 30s
labels:
release: prometheusextraManifests:
- apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: harbor-db
spec:
instances: 3
storage:
size: 10GiThis chart is a redesign, not a drop-in replacement. Migration steps:
- Backup your Harbor data using Harbor's built-in backup or database dumps
- Export your projects and artifacts if needed
- Deploy this chart as a new installation
- Migrate data using one of:
- Harbor's replication feature (recommended)
- Database migration with external tooling
- Re-push images from your CI/CD pipeline
A migration tool is available at harbor-helm-migration-tool/ to convert your legacy values.yaml:
# Build the tool
cd harbor-helm-migration-tool
go build -o bin/harbor-migrate ./cmd/harbor-migrate
# Convert values
./bin/harbor-migrate --input old-values.yaml --output new-values.yaml| Legacy Setting | New Setting |
|---|---|
expose.type: ingress |
ingress.enabled: true |
expose.ingress.* |
ingress.* |
database.type: internal |
Not supported - use external DB |
database.external.* |
database.* |
redis.type: internal |
valkey.enabled: true |
redis.external.* |
externalRedis.* |
persistence.imageChartStorage.* |
registry.storage.* |
nginx.* |
Not applicable - no nginx proxy |
notary.* |
Not supported - Notary deprecated |
chartmuseum.* |
Not supported - use OCI artifacts |
If you see validation errors, check:
externalURLmust be a valid URL starting withhttp://orhttps://database.hostis required- Resource values must be valid Kubernetes quantities
Check logs with:
kubectl logs -n harbor deploy/my-harbor-core
kubectl logs -n harbor deploy/my-harbor-registryCommon issues:
- Database connection failed - verify
database.hostand credentials - Redis connection failed - verify Valkey is running or
externalRedisconfig
The chart uses readOnlyRootFilesystem: true. If a component needs to write:
- Check if a writable volume mount is needed
- Volumes for
/tmpand other writable paths are pre-configured
helm unittest .helm lint . --set externalURL=https://example.com --set database.host=dbhelm template test . \
--set externalURL=https://example.com \
--set database.host=db \
--debugAutogenerated from chart metadata using helm-docs v1.14.2