Declarative Kubernetes Operator Runtime • Security-First • GitOps-Native
Orkestra is a declarative operator runtime: a platform for building Kubernetes operators using pure YAML. This Helm chart deploys:
helm repo add orkestra https://orkspace.github.io/orkestra
helm repo update
helm upgrade --install orkestra orkestra/orkestra \
--namespace orkestra-system \
--create-namespace
kubectl configured for your clusterork CLI installedcurl -sSL https://get.orkestra.sh | bash
Create a minimal Katalog (save as katalog.yaml):
apiVersion: orkestra.orkspace.io/v1
kind: Katalog
metadata:
name: my-platform
spec:
crds:
website:
enabled: true
apiTypes:
group: demo.orkestra.io
version: v1alpha1
kind: Website
plural: websites
operatorBox:
default: true
onCreate:
deployments:
- image: nginx
replicas: 1
If your CRD is not installed yet:
ork generate crd -f katalog.yaml -o website-crd.yaml
kubectl apply -f website-crd.yaml
Orkestra generates minimal RBAC from your Katalog — no wildcards, no excess permissions. The full flow is five steps, starting with an offline review before anything touches the cluster.
ork validate --full
Shows every RBAC rule, named profile, and startup dependency your Katalog will request — per CRD, per component (runtime and gateway), with inline notes explaining why each permission exists. No cluster required.
Review this output before generating. What you see here is exactly what the bundle will contain.
ork generate bundle -f katalog.yaml -o bundle.yaml
No cluster required. This produces a single YAML containing: a Namespace, ServiceAccounts for Runtime, Gateway, and Control Center, a ClusterRole with only the permissions your Katalog needs, a ClusterRoleBinding, and a ConfigMap with your Katalog data.
kubectl apply -f bundle.yaml
helm upgrade --install orkestra orkestra/orkestra \
--namespace orkestra-system \
--create-namespace
The chart automatically uses the ServiceAccount and ConfigMap you just applied.
kubectl get pods -n orkestra-system
kubectl get websites -A
Why this matters: Traditional operators are massively over-permissioned. Orkestra generates RBAC from your declared intent — least-privilege security by default.
ork validate --fullmakes that intent visible before a single resource is applied.
Orkestra’s runtime and gateway are independent. Deploy what you need.
| Topology | runtime.enabled |
gateway.enabled |
Use case |
|---|---|---|---|
| Runtime only | true |
false |
Operators without admission control |
| Gateway only | false |
true |
Deletion protection, namespace protection — no operators required |
| Full split | true |
true |
Operators with admission webhooks and protection |
The gateway runs standalone. No operator, no CRDs, no reconcile loop required. Use it to protect any existing Kubernetes resources.
# gateway-katalog.yaml
apiVersion: orkestra.orkspace.io/v1
kind: Katalog
metadata:
name: platform-security
author: platform-team
gateway:
standalone: true
security:
deletionProtection:
enabled: true
failurePolicy: Fail
cleanupOnShutdown: false
namespaceProtection:
enabled: true
failurePolicy: Fail
restrictedNamespaces:
- kube-system
- kube-public
certManager:
autoRotate: true
rotationThreshold: "30d"
validFor: "2y"
spec: {}
gateway.standalone: true — no runtime required. No spec.crds needed.
ork generate bundle --for gateway -f gateway-katalog.yaml | kubectl apply -f -
helm upgrade --install orkestra orkestra/orkestra \
--namespace orkestra-system \
--create-namespace \
--set runtime.enabled=false \
--set controlCenter.enabled=false \
--set gateway.enabled=true \
Label any resource orkestra.io/deletion-protection: "true" — the gateway intercepts every DELETE request for it at the API server.
In CI — all three steps run entirely offline, no cluster required:
ork validate --full -f katalog.yaml # review permissions before generating
ork generate bundle -f katalog.yaml -n orkestra-system -o orkestra-bundle.yaml
# Commit orkestra-bundle.yaml to your GitOps repo
ArgoCD or Flux syncs the bundle (RBAC + ConfigMap) and the Helm release. This is the cleanest, most auditable way to run Orkestra.
| Parameter | Description | Default |
|---|---|---|
runtime.enabled |
Deploy the runtime | true |
runtime.image.repository |
Runtime image | ghcr.io/orkspace/orkestra |
runtime.image.tag |
Image tag | Chart appVersion |
runtime.image.pullPolicy |
Pull policy | IfNotPresent |
runtime.replicaCount |
Replicas (leader election active) | 2 |
runtime.serviceAccount |
ServiceAccount name — must match generated RBAC | "orkestra" |
runtime.service.type |
Service type | ClusterIP |
runtime.service.port |
Service port | 8080 |
runtime.server.readTimeout |
HTTP read timeout (seconds) | 30 |
runtime.server.writeTimeout |
HTTP write timeout (seconds) | 60 |
runtime.config.logLevel |
Log level: debug, info, warn, error | info |
runtime.config.defaultWorkers |
Reconcile workers per CRD | 2 |
runtime.config.defaultResync |
Resync interval | 30s |
runtime.config.maxDepth |
Max workqueue depth per CRD | 500 |
runtime.config.failureThreshold |
Failures before degraded state | 10 |
runtime.config.environment |
Deployment environment | production |
runtime.leaderElection.enabled |
HA leader election | true |
runtime.leaderElection.leaseDuration |
Lease duration (seconds) | 15 |
runtime.leaderElection.renewDeadline |
Renew deadline (seconds) | 10 |
runtime.leaderElection.retryPeriod |
Retry period (seconds) | 5 |
runtime.katalog.inline |
Inline Katalog YAML | starter Katalog |
runtime.katalog.existingConfigMap |
Use a pre-existing ConfigMap | "" |
runtime.katalog.configMapKey |
Key inside the ConfigMap | katalog.yaml |
runtime.katalog.mountPath |
Mount path in container | /etc/orkestra/katalog |
runtime.gatewayEndpoint |
Gateway URL advertised on /katalog |
"" |
runtime.registry.enabled |
Enable OCI registry integration | false |
runtime.registry.url |
Registry URL | "" |
runtime.resources |
CPU/memory requests and limits | see values.yaml |
| Parameter | Description | Default |
|---|---|---|
gateway.enabled |
Deploy the gateway | false |
gateway.image.repository |
Gateway image | ghcr.io/orkspace/orkestra-gateway |
gateway.image.tag |
Image tag | Chart appVersion |
gateway.image.pullPolicy |
Pull policy | IfNotPresent |
gateway.replicaCount |
Replicas (stateless, no leader election) | 2 |
gateway.serviceAccount |
ServiceAccount name | "orkestra-gateway" |
gateway.service.type |
Service type | ClusterIP |
gateway.server.httpPort |
Health + metrics port | 8080 |
gateway.server.httpsPort |
Webhook (HTTPS) port | 8443 |
gateway.config.logLevel |
Log level | info |
gateway.webhooks.enabled |
Master switch for webhooks | false |
gateway.webhooks.admission |
Enable admission webhook | false |
gateway.webhooks.conversion |
Enable conversion webhook | false |
gateway.katalog.existingConfigMap |
ConfigMap containing the gateway Katalog | "" |
gateway.katalog.configMapKey |
Key inside the ConfigMap | katalog.yaml |
gateway.resources |
CPU/memory requests and limits | see values.yaml |
| Parameter | Description | Default |
|---|---|---|
controlCenter.enabled |
Deploy the Control Center | true |
controlCenter.image.repository |
Control Center image | ghcr.io/orkspace/orkestra-cc |
controlCenter.replicaCount |
Replicas | 1 |
controlCenter.serviceAccount |
ServiceAccount name | "orkestra-cc" |
controlCenter.config.orkestraURLs |
Runtime URLs to monitor | [] |
controlCenter.config.refreshInterval |
Katalog refresh interval | 10s |
controlCenter.config.logLevel |
Log level | info |
controlCenter.service.type |
Service type | ClusterIP |
controlCenter.service.port |
Service port | 8081 |
controlCenter.ingress.enabled |
Enable Ingress | false |
controlCenter.ingress.className |
Ingress class | "" |
controlCenter.ingress.hosts |
Ingress hosts | [] |
controlCenter.ingress.tls |
TLS configuration | [] |
| Parameter | Description | Default |
|---|---|---|
global.namespace |
Namespace hint for all components | orkestra-system |
global.nameOverride |
Override chart name | "" |
global.fullnameOverride |
Override full resource name | "" |
All keys below apply independently under runtime:, gateway:, and controlCenter:.
| Parameter | Description | Default |
|---|---|---|
<component>.imagePullSecrets |
Image pull secrets | [] |
<component>.hpa.enabled |
Enable HorizontalPodAutoscaler | false |
<component>.hpa.minReplicas |
HPA minimum replicas | 2 |
<component>.hpa.maxReplicas |
HPA maximum replicas | 5 |
<component>.hpa.targetCPUUtilizationPercentage |
HPA CPU target | 80 |
<component>.hpa.targetMemoryUtilizationPercentage |
HPA memory target | 80 |
<component>.networkPolicy.enabled |
Enable NetworkPolicy | false |
<component>.networkPolicy.ingressFrom |
Allowed ingress sources | [] |
<component>.pdb.enabled |
Enable PodDisruptionBudget | true |
<component>.pdb.minAvailable |
Minimum available pods | 1 |
<component>.nodeSelector |
Node selector | {} |
<component>.tolerations |
Tolerations | [] |
<component>.affinity |
Affinity rules | {} |
<component>.topologySpreadConstraints |
Topology spread constraints | [] |
<component>.extraEnv |
Extra environment variables | [] |
<component>.extraEnvFrom |
Extra env sources (ConfigMap/Secret) | [] |
<component>.extraVolumes |
Extra volumes | [] |
<component>.extraVolumeMounts |
Extra volume mounts | [] |
<component>.podAnnotations |
Pod annotations | {} |
<component>.podLabels |
Extra pod labels | {} |
# production-values.yaml
runtime:
replicaCount: 3
serviceAccount: orkestra
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
config:
logLevel: warn
defaultWorkers: 4
defaultResync: 1m
katalog:
existingConfigMap: platform-katalog
gatewayEndpoint: "http://orkestra-gateway.orkestra-system.svc:8080"
hpa:
enabled: true
minReplicas: 3
maxReplicas: 8
targetCPUUtilizationPercentage: 70
networkPolicy:
enabled: true
ingressFrom:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
gateway:
enabled: true
replicaCount: 2
serviceAccount: orkestra-gateway
katalog:
existingConfigMap: platform-katalog
hpa:
enabled: true
minReplicas: 2
maxReplicas: 5
controlCenter:
replicaCount: 2
serviceAccount: orkestra-cc
config:
orkestraURLs:
- http://orkestra-runtime:8080
refreshInterval: 30s
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: control-center.platform.myorg.io
paths:
- path: /
pathType: Prefix
tls:
- secretName: control-center-tls
hosts:
- control-center.platform.myorg.io
hpa:
enabled: true
minReplicas: 2
maxReplicas: 5
helm upgrade --install orkestra orkestra/orkestra \
--namespace orkestra-system --create-namespace \
--values production-values.yaml
# Runtime
kubectl port-forward svc/orkestra-runtime 8080:8080 -n orkestra-system
curl localhost:8080/health
curl localhost:8080/metrics
curl localhost:8080/katalog | jq
# Gateway
kubectl port-forward svc/orkestra-gateway 8080:8080 -n orkestra-system
curl localhost:8080/health
curl localhost:8080/metrics
# Control Center
kubectl port-forward svc/orkestra-cc 8081:8081 -n orkestra-system
open http://localhost:8081/controlcenter
Prometheus scrape configuration:
- job_name: orkestra-runtime
static_configs:
- targets: ['orkestra-runtime.orkestra-system.svc.cluster.local:8080']
- job_name: orkestra-gateway
static_configs:
- targets: ['orkestra-gateway.orkestra-system.svc.cluster.local:8080']
- job_name: orkestra-control-center
static_configs:
- targets: ['orkestra-cc.orkestra-system.svc.cluster.local:8081']
helm repo update
helm upgrade orkestra orkestra/orkestra \
--namespace orkestra-system \
--values values.yaml
helm uninstall orkestra --namespace orkestra-system
Uninstalling removes Deployments and Services. CRDs and custom resources Orkestra was managing are not deleted — they remain in the cluster and must be cleaned up separately.
Control Center cannot connect to Runtime
kubectl get svc -n orkestra-system
kubectl logs -n orkestra-system deployment/orkestra-cc
Ensure controlCenter.config.orkestraURLs points to the runtime service.
Katalog not loaded
kubectl get configmap -n orkestra-system
kubectl logs -n orkestra-system deployment/orkestra-runtime | grep -i katalog
RBAC permission denied
ork generate bundle -f katalog.yaml -o bundle.yaml
kubectl apply -f bundle.yaml
Webhooks not working
kubectl get validatingwebhookconfigurations
kubectl logs -n orkestra-system deployment/orkestra-gateway | grep -i webhook
Two binaries, two build tags, two attack surfaces:
| Command | Developer CLI | Runtime binary | Gateway binary |
|---|---|---|---|
ork run |
✓ | ✓ | — |
ork gate |
— | — | ✓ |
ork version |
✓ | ✓ | ✓ |
ork generate |
✓ | — | — |
ork validate |
✓ | — | — |
ork init |
✓ | — | — |
A compromised runtime container cannot serve webhooks. A compromised gateway container cannot generate RBAC bundles or enumerate CRDs. Each binary does exactly one thing.
Orkestra never auto-creates ServiceAccount, ClusterRole, or ClusterRoleBinding resources. You generate them from your Katalog, review the output, commit it, and apply it explicitly. Every permission is visible in source control before it reaches the cluster.
Label any resource orkestra.io/deletion-protection: "true". The gateway intercepts every DELETE request at the API server before it reaches etcd. The webhook configuration is self-healing — if deleted, the housekeeper recreates it within the configured sync interval.
The gateway generates its own TLS certificate and rotates it pre-emptively before expiry. Configure via the Katalog:
security:
certManager:
autoRotate: true
rotationThreshold: "30d"
validFor: "2y"
To supply your own certificate: set TLS_CERT and TLS_KEY in the gateway environment — Orkestra will not touch the cert lifecycle.
| Traditional operators | Orkestra |
|---|---|
Wildcard permissions (*/*/*) |
Minimal, derived from your Katalog |
| RBAC written by hand | Generated automatically |
| Drifts over time | Always in sync |
| Over-permissioned by default | Least privilege by default |
| One binary does everything | Runtime runs, Gateway gates |
Same license as the Orkestra project.