Helm Chart Reference
Every field in values.yaml for the maestro Helm chart. Defaults shown are from the chart (charts/maestro/values.yaml).
Image
A single unified image runs both workloads. The chart picks the entrypoint.
image:
repository: public.ecr.aws/cardinalhq.io/maestro
tag: "" # empty → Chart.appVersion (recommended: pin to a released tag)
pullPolicy: IfNotPresent| Field | Default | Notes |
|---|---|---|
image.repository | public.ecr.aws/cardinalhq.io/maestro | Also mirrored to ghcr.io/cardinalhq/maestro |
image.tag | "" (falls back to Chart.appVersion) | Pin this. Do not let it float. |
image.pullPolicy | IfNotPresent | Kubernetes image pull policy |
Maestro workload
maestro:
enabled: true
replicas: 1
port: 4200
resources: {}
labels: {}
annotations: {}
nodeSelector: {}
tolerations: []
affinity: {}
env: [] # extra env vars (auth, LLM, etc.)
extraVolumes: []
extraVolumeMounts: []| Field | Default | Notes |
|---|---|---|
maestro.enabled | true | Disable only if you want to run the gateway alone |
maestro.replicas | 1 | Maestro is stateless but session cookies are not sticky-bound — keep at 1 unless you’ve set up shared session storage |
maestro.port | 4200 | Container port; also the Service port |
maestro.env | [] | This is where you put OIDC, LLM, and MAESTRO_BASE_URL vars. See Environment variables |
maestro.extraVolumes / extraVolumeMounts | [] | Mount extra config or certs into the pod |
The Maestro pod runs with readOnlyRootFilesystem: true. A tmp emptyDir is mounted at /tmp. If you need additional writable paths (e.g., ontology workspaces), add them via extraVolumes + extraVolumeMounts.
An initContainer waits for the MCP gateway service to accept connections before Maestro starts.
MCP Gateway workload
Starting with chart 0.8.0, the MCP gateway runs as a Kubernetes native sidecar (initContainer with restartPolicy: Always) inside every Maestro pod and every github-cache pod. There is no standalone Deployment or Service by default — Maestro talks to it over localhost, so every MCP session stays pod-local. The mcpGateway.enabled and mcpGateway.replicas fields from older chart versions no longer exist.
mcpGateway:
port: 8080
debugPort: 9090
apiKey: ""
apiKeySecret:
name: ""
key: "MCP_API_KEY"
service:
enabled: false
resources: {}
env: []| Field | Default | Notes |
|---|---|---|
mcpGateway.port | 8080 | Sidecar listen port. Maestro reaches it at http://localhost:8080. |
mcpGateway.debugPort | 9090 | pprof / debug endpoints — do not expose externally. |
mcpGateway.apiKey | "" | Plaintext value injected as MCP_API_KEY. Convenient for local dev; leak risk in checked-in values. |
mcpGateway.apiKeySecret.name | "" | Name of an existing Secret holding the API key. The chart injects it as valueFrom.secretKeyRef. apiKey wins when both are set. |
mcpGateway.apiKeySecret.key | "MCP_API_KEY" | Key within the Secret. |
mcpGateway.service.enabled | false | When true, creates <release>-mcp-gateway ClusterIP Service targeting the sidecar’s port. Only useful for single-pod installs; for any multi-replica deployment, expose the MCP entry point through Maestro instead (MAESTRO_MCP_API_KEY on the Maestro container — see Connect AI Clients). |
mcpGateway.env | [] | Extra env for the sidecar (e.g. AWS_REGION for Bedrock embeddings). |
Leave apiKey and apiKeySecret.name both empty for most installs — the sidecar runs unauthenticated on localhost, and external auth is enforced at the Maestro entry point via MAESTRO_MCP_API_KEY. The mcpGateway.apiKey* knobs only matter if you also flipped service.enabled: true to expose the sidecar directly (a single-pod-only path; in multi-replica installs, route external MCP traffic through Maestro per Connect AI Clients).
Database
Required. Maestro will not start without it.
database:
secretName: "pg-credentials"
create: true
passwordKey: "MAESTRO_DB_PASSWORD"
host: "" # required
port: 5432
name: "maestro"
username: "maestro"
password: "" # only used when create: true
sslMode: "require"| Field | Default | Notes |
|---|---|---|
database.create | true | When true, the chart creates a Secret named <release>-<secretName> with password base64-encoded. In prod, set to false and manage the secret yourself (SealedSecrets, External Secrets, etc.) |
database.secretName | pg-credentials | When create: true, gets prefixed with the release name. When create: false, used verbatim |
database.passwordKey | MAESTRO_DB_PASSWORD | Key within the secret |
database.host | "" | Required. PostgreSQL hostname |
database.port | 5432 | |
database.name | maestro | Database name (must exist; Maestro does not create it) |
database.username | maestro | |
database.sslMode | require | One of disable, require, verify-ca, verify-full |
The chart synthesizes MAESTRO_DATABASE_URL from these fields and injects it into both workloads — you do not need to set it yourself.
Ingress
ingress:
enabled: false
className: ""
host: ""
annotations: {}
tls: []The built-in Ingress is a minimal networking.k8s.io/v1 object that points at the Maestro service. It works for NGINX, Traefik, or anything that honors ingressClassName. If you need more control (IngressRoute, Gateway API, multiple hosts), disable this and create your own routing resource — the Maestro service is <release>-maestro on port 4200.
Service account
serviceAccount:
create: true
name: ""
annotations: {}| Field | Default | Notes |
|---|---|---|
serviceAccount.create | true | Creates the SA used by both workloads |
serviceAccount.name | "" | Override the generated name |
serviceAccount.annotations | {} | Add IRSA annotation here for Bedrock. See AWS Bedrock |
Global
Applied to both workloads.
global:
imagePullSecrets: []
env: [] # env vars merged into both containers
labels: {}
annotations: {}
podSecurityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
containerSecurityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault| Field | Default | Notes |
|---|---|---|
global.imagePullSecrets | [] | List of secret names for pulling from private registries |
global.env | [] | Env vars that apply to both Maestro and the gateway. Use for AWS_REGION, tracing config, etc. |
global.labels | {} | Merged into every resource’s labels |
global.annotations | {} | Merged into every resource’s annotations |
global.podSecurityContext | runAsNonRoot: true, UID/GID/fsGroup 65532 | Pod-level securityContext applied to every workload. Per-component overrides at <component>.podSecurityContext win, missing fields fall back to the globals |
global.containerSecurityContext | no-privilege-escalation, read-only rootfs, drop ALL caps, RuntimeDefault seccomp | Container-level hardening applied to every container. Per-component overrides at <component>.containerSecurityContext |
Scheduling fields (nodeSelector, tolerations, affinity) can also be set at the global level and are merged with per-workload overrides.
Deploying on OpenShift
The chart renders cleanly under the restricted-v2 SCC once the UID fields are nulled out so the SCC can inject values from the namespace’s assigned UID range:
global:
podSecurityContext:
runAsNonRoot: true
runAsUser: null
runAsGroup: null
fsGroup: nullWith that in place the pod securityContext emits only runAsNonRoot: true; the SCC fills in runAsUser, runAsGroup, and fsGroup. All other hardening from global.containerSecurityContext (no-privilege-escalation, drop ALL, RuntimeDefault seccomp, read-only rootfs) stays in effect.
The built-in Ingress is a standard networking.k8s.io/v1 resource with a configurable ingressClassName — the OpenShift HAProxy router handles it out of the box; no nginx-specific annotations are emitted.
Reach out to support@cardinalhq.io for support or to ask questions not answered in our documentation.