Helm chart values

The stageset-controller Helm chart lives in the
metio/helm-charts
monorepo and is published at oci://ghcr.io/metio/helm-charts/stageset-controller.
The table below is generated from the chart’s values.schema.json, so it tracks
the chart’s current schema rather than a hand-maintained copy.
For how the values map onto the binary’s runtime behaviour, see the
Configuration reference
— every controller.*
value drives the corresponding --flag.
| Value | Type | Default | Description |
|---|---|---|---|
affinity | object | (empty) | Affinity rules for scheduling. |
autoscaling | object | (empty) | HorizontalPodAutoscaler (rendered only when replicas.max > replicas.min). |
autoscaling.targetCPUUtilizationPercentage | integer | 80 | |
cleanupOnDelete | object | (empty) | Pre-delete cleanup hook. StageSets carry a finalizer (stages.metio.wtf/finalizer) that tears down the objects a StageSet applied. A plain `helm uninstall` removes the controller while StageSets still hold that finalizer, so their delete never completes and uninstall hangs. When enabled, a pre-delete Job bulk-deletes every StageSet so the controller's finalizer runs (tearing down applied objects) BEFORE Helm removes the controller itself. Disable when uninstall is driven by something other than Helm (e.g., ArgoCD with hooks off) — the manual cleanup pattern is: kubectl delete stageset --all -A --wait=true --timeout=2m helm uninstall stageset-controller -n <ns> |
cleanupOnDelete.activeDeadlineSeconds | integer | 600 | Job-level safety net. activeDeadlineSeconds caps the Job's total runtime; if the controller is wedged, Helm won't be blocked forever. |
cleanupOnDelete.backoffLimit | integer | 2 | backoffLimit on the pre-delete Job. |
cleanupOnDelete.enabled | boolean | true | |
cleanupOnDelete.image | object | (empty) | Image for the pre-delete cleanup Job (a kubectl image). |
cleanupOnDelete.image.pullPolicy | string | IfNotPresent | |
cleanupOnDelete.image.registry | string | registry.k8s.io | |
cleanupOnDelete.image.repository | string | kubectl | |
cleanupOnDelete.image.tag | string | v1.34.0 | renovate: datasource=docker depName=registry.k8s.io/kubectl |
cleanupOnDelete.kubectlTimeout | string | 2m | Maximum wall-clock time the pre-delete Job's `kubectl delete stagesets all --wait` waits for the controller's finalizers to tear the StageSets down. On timeout the delete (and the Job) FAILS, blocking the uninstall so finalizers are never silently skipped — the Job does not force-strip them; recover by stripping finalizers by hand (see the job's comments) and re-running uninstall. Bigger when StageSets tear down many objects or talk to slow remote clusters. |
cleanupOnDelete.resources | object | (empty) | Resource requests/limits for the cleanup Job container. |
cleanupOnDelete.resources.cpu | string | 100m | |
cleanupOnDelete.resources.ephemeralStorage | string | 16Mi | Ephemeral-storage requests/limits. The cleanup Job runs a few `kubectl` calls then exits — disk usage is negligible. Sized small to satisfy kube-score's resource-completeness check without crowding cluster ephemeral-storage budgets. |
cleanupOnDelete.resources.memory | string | 64Mi | |
controller | object | (empty) | Controller behaviour (maps onto the binary's flags). |
controller.allowedActionHosts | array | (empty) | Host globs permitted for http actions (SSRF guard). Loopback/link-local are always denied unless explicitly listed here. |
controller.defaultInterval | string | 10m | Reconcile cadence for StageSets that omit spec.interval. Most StageSets can leave spec.interval unset and inherit this. |
controller.inventoryShardCap | integer | 5000 | Maximum entries per StageInventory shard. |
controller.leaderElect | boolean | true | Leader election for HA replicas. Keep on; harmless single-replica. |
controller.logFormat | string | json | Log output format for the controller binary (json, text). |
controller.logLevel | string | info | Log verbosity for the controller binary (debug, info, warn, error). |
controller.noCrossNamespaceRefs | boolean | false | Deny cross-namespace sourceRef / dependsOn references. |
controller.objectLevelKMS | boolean | false | Decrypt SOPS cloud KMS keys under each StageSet's serviceAccountName federated to a cloud identity, instead of the controller's ambient credentials. Opt-in per-tenant KMS identity: the tenant ServiceAccount must be federated (IRSA / Workload Identity) to a cloud identity granted KMS decrypt. Off by default (ambient credentials). |
controller.requirePinnedMigrationSources | boolean | false | Require a spec.migrationsSourceRef source to be pinned to an immutable revision (OCIRepository spec.ref.digest / GitRepository spec.ref.commit) before its destructive migration ladder runs, so a tag/branch overwrite can't auto-roll new destructive content. When off, a mutable-pinned source still runs but emits a Warning event. Recommended in production. |
controller.requireVerifiedMigrationSources | boolean | false | Require a spec.migrationsSourceRef source to be signature-verified (status.conditions[SourceVerified]=True from the source's spec.verify cosign/notation config) before its destructive migration ladder runs. A source whose verification FAILED is always refused; this additionally refuses sources that configure no verification at all. Recommended in production. |
controller.tracing | object | (empty) | OpenTelemetry tracing for the controller. When endpoint is empty (the default), no spans are emitted and the OTel SDK is in no-op mode. Set the endpoint to ship spans to an OTLP gRPC collector: tracing: endpoint: otel-collector.observability.svc:4317 insecure: true |
controller.tracing.endpoint | string | (empty) | OTLP gRPC collector host:port. Empty disables tracing entirely. |
controller.tracing.insecure | boolean | false | Skip TLS when dialing the OTLP collector. Use only for in-cluster collectors that don't terminate TLS themselves. |
controller.tracing.sampleRatio | number | 1 | TraceID-ratio sampling (0.0..1.0). 1.0 samples every trace. |
controller.watchNamespaces | array | (empty) | Namespaces this controller watches. Empty (the default) means cluster-wide. When set, the manager's cache only observes StageSets and sources in these namespaces, and RBAC pivots from a cluster-wide ClusterRoleBinding to one RoleBinding per listed namespace — the multi-tenant controller-instances pattern (one release per tenant-group, disjoint watch sets). |
crds | object | (empty) | CRD lifecycle. The chart ships the controller's CRDs under templates/ so a `helm upgrade` applies schema changes automatically. Set create=false to manage them out-of-band — e.g. pre-installed cluster-wide, or in CI where the same chart is installed once per values file and a kept cluster-scoped CRD owned by the first release can't be re-adopted by the next. |
crds.create | boolean | true | |
deployment | object | (empty) | Extra labels and revision history for the Deployment. |
deployment.additionalLabels | object | (empty) | |
deployment.revisionHistoryLimit | integer | 10 | |
gate | object | (empty) | Flagger stage-gate endpoint (GET /gate/{ns}/{stageset}/{stage}). Renders a dedicated Service when enabled. |
gate.enabled | boolean | true | |
global | object | (empty) | Global values are values that can be accessed from any chart or subchart by exactly the same name. |
image | object | (empty) | Container image. tag defaults to .Chart.AppVersion (the released controller version); override only for a custom build. |
image.pullPolicy | string | IfNotPresent | Pull policy. IfNotPresent suits tagged release images; switch to Always for a mutable tag (latest, edge). |
image.registry | string | ghcr.io | |
image.repository | string | metio/stageset-controller | |
image.tag | string | (empty) | Explicit tag override; empty defaults to .Chart.AppVersion. |
imagePullSecrets | array | (empty) | Image pull secrets for a private registry. |
mcp | object | (empty) | MCP (Model Context Protocol) server. Exposes the controller's read-only introspection tools over a streamable-HTTP endpoint. Renders the server flag, container port, and a dedicated Service when enabled. |
mcp.allowMutations | boolean | false | Additionally register the gated write tools. Off keeps the server read-only. |
mcp.enabled | boolean | false | Render the MCP server flag, container port, and Service. |
mcp.service | object | (empty) | Dedicated MCP Service. Rendered with the rest of the MCP wiring when mcp.enabled. |
mcp.service.enabled | boolean | true | |
metrics | object | (empty) | Prometheus integration. |
metrics.prometheusRule | object | (empty) | Opt-in PrometheusRule with a starter alert set on the custom stageset_* metrics plus controller-runtime signals. Requires the Prometheus operator CRDs. |
metrics.prometheusRule.annotations | object | (empty) | Extra annotations on the PrometheusRule object. |
metrics.prometheusRule.enabled | boolean | false | |
metrics.prometheusRule.extraAlertLabels | object | (empty) | Labels merged onto EVERY rendered alert — route all stageset alerts through one Alertmanager receiver (e.g. team: platform). |
metrics.prometheusRule.extraRules | array | (empty) | Extra alert rules appended under a separate "stageset-extras" group, rendered verbatim. To silence a built-in alert, raise its threshold below to an impossibly high value rather than forking the chart. |
metrics.prometheusRule.interval | string | 30s | Evaluation interval for every rule group. |
metrics.prometheusRule.labels | object | (empty) | Extra labels on the PrometheusRule object (typically what your Prometheus selects on). |
metrics.prometheusRule.runbookAnnotationKey | string | runbook_url | Annotation key the runbook URL lands under (runbook_url is the Prometheus-operator convention). |
metrics.prometheusRule.thresholds | object | (empty) | Numeric/duration thresholds in one place so operators can tune the noise floor without editing rule bodies. |
metrics.prometheusRule.thresholds.budgetFrozenDuration | string | 15m | StageSetBudgetFrozen / StageSetStageBudgetFrozen: how long an error-budget freeze (rollout-wide or per-stage) must hold before alerting. Deploys held out of SLO budget are intentional, so this is a heads-up, not an instant page. |
metrics.prometheusRule.thresholds.metricSourceErrorsDuration | string | 15m | How long the per-hour metric-source error increase must stay above the threshold before the alert fires. |
metrics.prometheusRule.thresholds.metricSourceErrorsPerHour | integer | 3 | StageSetMetricSourceUnavailable: hourly increase on the metric-source error counter before alerting. A fail-open freeze runs with no protection while its source is broken, so this is the loud signal for that hole. |
metrics.prometheusRule.thresholds.podDownDuration | string | 5m | StageSetControllerPodDown: NotReady duration before paging. Below 5m it fires during normal pod rolls. |
metrics.prometheusRule.thresholds.promotionBlockedDuration | string | 10m | StageSetPromotionBlocked: how long a promotion analysis must stay failed (breached past failureLimit) before alerting. |
metrics.prometheusRule.thresholds.promotionPendingDuration | string | 1h | StageSetPromotionStuck: how long a rollout may sit parked at a stage (soak/analysis/manual hold) before alerting. Long by default so a normal short soak never fires — it targets a forgotten manual gate or a misconfigured soak. Raise it where long deliberate holds are expected. |
metrics.prometheusRule.thresholds.reconcileErrorDuration | string | 10m | |
metrics.prometheusRule.thresholds.reconcileErrorRate | number | 0.1 | StageSetReconcileErrorsHigh: per-StageSet Ready=False rate (per second, 5m window). 0.1/s ≈ one failure every 10s — a stuck StageSet, not a blip. |
metrics.prometheusRule.thresholds.reconcileLatencyDuration | string | 15m | |
metrics.prometheusRule.thresholds.reconcileLatencySeconds | integer | 30 | StageSetReconcileLatencyHigh: reconcile p99 ceiling, seconds. 30s is generous — an artifact fetch plus a multi-stage apply. |
metrics.prometheusRule.thresholds.watchEngagementFailuresDuration | string | 30m | How long the per-hour watch-engagement failure increase must stay above the threshold before the alert fires. |
metrics.prometheusRule.thresholds.watchEngagementFailuresPerHour | integer | 1 | StageSetWatchEngagementFailing: hourly increase on the producer-watch engagement failure counter. The retry pipeline eventually re-engages, but a sustained inability to engage means dependent StageSets aren't re-triggering on upstream producer republishes. |
metrics.prometheusRule.thresholds.webhookCertRenewalFailuresDuration | string | 30m | |
metrics.prometheusRule.thresholds.webhookCertRenewalFailuresPerHour | integer | 1 | StageSetWebhookCertRenewalFailing: self-signed cert renewal failures per hour before paging. |
metrics.prometheusRule.thresholds.workqueueDepth | integer | 50 | StageSetControllerWorkqueueDepthHigh: queued reconcile requests before alerting. 50 absorbs a normal burst, low enough to catch a real stall. |
metrics.prometheusRule.thresholds.workqueueDuration | string | 15m | |
metrics.serviceMonitor | object | (empty) | |
metrics.serviceMonitor.additionalLabels | object | (empty) | |
metrics.serviceMonitor.enabled | boolean | false | |
metrics.serviceMonitor.interval | string | 30s | |
namespace | object | (empty) | Install namespace with Pod-Security labels. The chart installs into .Release.Namespace; set create to render it with PSS labels. |
namespace.create | boolean | false | |
namespace.pssLevel | string | restricted | |
networkPolicy | object | (empty) | Opt-in NetworkPolicy. Inert without a NetworkPolicy controller. |
networkPolicy.additionalIngress | array | (empty) | Extra ingress rules merged verbatim. Applies to the kubernetes engine only. |
networkPolicy.calico | object | (empty) | Calico engine (engine=calico) raw passthrough. Entries are merged verbatim into the projectcalico.org NetworkPolicy's spec.ingress / spec.egress. |
networkPolicy.calico.egress | array | (empty) | Extra spec.egress rules merged verbatim (Calico NetworkPolicy schema). |
networkPolicy.calico.ingress | array | (empty) | Extra spec.ingress rules merged verbatim (Calico NetworkPolicy schema). |
networkPolicy.cilium | object | (empty) | Cilium engine (engine=cilium) raw passthrough. Entries are merged verbatim into the CiliumNetworkPolicy's spec.ingress / spec.egress. This is how you tighten the cilium policy — e.g. add fromEndpoints to the ingress rules, or additional toEndpoints / toFQDNs egress. |
networkPolicy.cilium.egress | array | (empty) | Extra spec.egress rules merged verbatim (CiliumNetworkPolicy schema). |
networkPolicy.cilium.ingress | array | (empty) | Extra spec.ingress rules merged verbatim (CiliumNetworkPolicy schema). |
networkPolicy.clusterNetworkPolicy | object | (empty) | ClusterNetworkPolicy engine (engine=clusterNetworkPolicy) raw passthrough. Entries are merged verbatim into the policy.networking.k8s.io ClusterNetworkPolicy's spec.ingress / spec.egress. |
networkPolicy.clusterNetworkPolicy.egress | array | (empty) | Extra spec.egress rules merged verbatim (ClusterNetworkPolicy schema). |
networkPolicy.clusterNetworkPolicy.ingress | array | (empty) | Extra spec.ingress rules merged verbatim (ClusterNetworkPolicy schema). |
networkPolicy.clusterNetworkPolicy.priority | integer | 1000 | spec.priority on the ClusterNetworkPolicy (lower wins within the tier). |
networkPolicy.defaultDeny | object | (empty) | Render a namespace-wide default-deny policy so EVERY pod in the namespace is denied by default and the per-workload allowlists are the only exceptions (zero-trust namespace). OFF by default because it also denies co-located workloads — only enable it when the controller has its own namespace. Leaving it off keeps a pod-scoped setup: only this controller's pods are locked down, neighbours untouched. |
networkPolicy.defaultDeny.enabled | boolean | false | |
networkPolicy.defaultDeny.order | integer | 2000 | Calico/ClusterNetworkPolicy sort key for the deny-all. It must rank the deny-all AFTER the per-workload allowlists so the allowlists win: Calico evaluates lower order first (the allowlist policies carry no order = lowest precedence, so this defaults high), and ClusterNetworkPolicy gives lower priority numbers higher precedence (the allowlist priority defaults to networkPolicy.clusterNetworkPolicy.priority, so this defaults higher). The kubernetes and cilium engines have no precedence knob — deny + allow simply combine additively, allow wins. |
networkPolicy.egress | object | (empty) | |
networkPolicy.egress.dns | boolean | true | When egress is enabled, allow DNS (UDP+TCP 53) to the cluster DNS namespace. |
networkPolicy.egress.dnsNamespace | string | kube-system | Namespace of the cluster DNS service (matched by kubernetes.io/metadata.name). |
networkPolicy.egress.enabled | boolean | false | Render egress rules. Adds Egress to policyTypes, so everything not explicitly allowed is denied. Off by default: enable only after allowing everything the controller needs (DNS, the kube-apiserver, source-controller / producer namespaces, S3, the OTLP collector, and any remote-cluster or http-action targets) via egress.to — enabling egress without allowing the apiserver cuts the controller off from the cluster. |
networkPolicy.egress.to | array | (empty) | Additional egress peers merged verbatim into spec.egress. This is where you allow the kube-apiserver (an ipBlock CIDR), the source-controller / producer namespaces, and external endpoints (S3, OTLP, remote-cluster or http-action targets). NetworkPolicy cannot select the apiserver by label, so it must be an ipBlock here. |
networkPolicy.enabled | boolean | false | Render the NetworkPolicy. When enabled, the policy targets the controller pod; with a NetworkPolicy controller (Calico/Cilium/etc.) installed, anything not listed below is denied. Leave OFF on clusters without such a controller — the policy is silently inert there. |
networkPolicy.engine | string | kubernetes | Policy engine to render for. kubernetes (default) emits a vanilla networking.k8s.io NetworkPolicy. cilium / calico / clusterNetworkPolicy emit the engine-native equivalent instead (CiliumNetworkPolicy, projectcalico.org NetworkPolicy, or policy.networking.k8s.io ClusterNetworkPolicy). The per-port .from knobs below apply to the kubernetes engine only; alternative engines are pod-scoped allow-all on the required ports and tighten via their native passthrough lists (cilium/calico/clusterNetworkPolicy .ingress / .egress). |
networkPolicy.gate | object | (empty) | Sources allowed to reach the gate port. Empty = all (e.g. Flagger for the gate). |
networkPolicy.gate.from | array | (empty) | |
networkPolicy.mcp | object | (empty) | Sources allowed to reach the MCP server port. Empty = all. Only rendered when mcp.enabled. |
networkPolicy.mcp.from | array | (empty) | |
networkPolicy.metrics | object | (empty) | Sources allowed to reach the metrics port. Empty = all (e.g. Prometheus for metrics). |
networkPolicy.metrics.from | array | (empty) | |
networkPolicy.webhook | object | (empty) | Sources allowed to reach the webhook port. Empty = all (e.g. kube-apiserver for the webhook, which cannot be a podSelector). Applies to the kubernetes engine only. |
networkPolicy.webhook.from | array | (empty) | |
nodeSelector | object | (empty) | Node selector for scheduling. |
pod | object | (empty) | Extra labels for the pod template. |
pod.additionalLabels | object | (empty) | |
podDisruptionBudget | object | (empty) | PodDisruptionBudget (rendered for any multi-replica install — replicas.min above 1, or replicas.max above replicas.min). |
podDisruptionBudget.maxUnavailable | integer|string|null | null | PDB maxUnavailable. Null (default) renders minAvailable instead; set this (and minAvailable to null) to cap voluntary disruptions to N pods at a time. |
podDisruptionBudget.minAvailable | integer|string|null | 1 | PDB minAvailable (the default shape). Mutually exclusive with maxUnavailable — set this to null when using maxUnavailable. |
podDisruptionBudget.unhealthyPodEvictionPolicy | string | (empty) | spec.unhealthyPodEvictionPolicy on the PDB. Empty defers to the cluster default. |
podSecurityContext | object | (empty) | Pod-level securityContext (PSS restricted). fsGroup matches the container's runAsGroup so the kubelet/CSI driver chowns mounted volumes (notably the rollbackStore.backend=pvc volume) to the running group — without it the non-root container (uid/gid 65532) cannot write to a freshly provisioned, root-owned PVC and the controller crashloops at startup. fsGroupChangePolicy OnRootMismatch skips the recursive chown when the volume root already matches, avoiding slow restarts on large volumes. |
podSecurityContext.fsGroup | integer | 65532 | |
podSecurityContext.fsGroupChangePolicy | string | OnRootMismatch | |
podSecurityContext.runAsNonRoot | boolean | true | |
podSecurityContext.seccompProfile | object | (empty) | |
podSecurityContext.seccompProfile.type | string | RuntimeDefault | |
ports | object | (empty) | Ports the controller binds. |
ports.gate | integer | 8082 | read-only Flagger stage-gate endpoint |
ports.health | integer | 8081 | /healthz + /readyz probes |
ports.mcp | integer | 8084 | MCP (Model Context Protocol) server (mcp.enabled only) |
ports.metrics | integer | 8080 | controller-runtime Prometheus metrics |
ports.webhook | integer | 9443 | validating admission webhook |
rbac | object | (empty) | Controller RBAC. The default grant is deliberately minimal: the controller impersonates each StageSet's spec.serviceAccountName for cluster writes, so a tenant's own RBAC bounds what its StageSets can touch. |
rbac.clusterAdmin | boolean | false | Bind the controller ServiceAccount to the built-in cluster-admin ClusterRole. Single-tenant clusters where StageSets omit spec.serviceAccountName run the controller under its own identity; cluster-admin lets it apply any kind cluster-wide — the model Flux's helm-controller uses in its default install. Leave false for multi-tenant clusters, where every StageSet sets serviceAccountName and the controller only impersonates those scoped SAs. |
rbac.ledgerView | boolean | false | Aggregate read (get/list/watch) on StageLedgers into the built-in `view` ClusterRole, so a namespace user who can already view their StageSets can see why a scope: Lifetime action was skipped (which bootstrap already ran). Off by default: it widens what `view` exposes, a deliberate choice on a multi-tenant permission surface. Enabling it grants read only — never write; forgetting a completion stays an operator action via `stagesetctl reset-ledger`. |
replicas | object | (empty) | Replica bounds. min is the Deployment replica count; when max > min an HPA and a PodDisruptionBudget are rendered. At the default 1/1 both are omitted. |
replicas.max | integer | 1 | |
replicas.min | integer | 1 | |
resources | object | (empty) | Pod resource sizing. Applied to both requests and limits (cpu, memory, and ephemeral-storage) so the pod is fully constrained — kube-score's resource-completeness checks require all three on both sides. ephemeralStorage must exceed tmpSizeLimit (the /tmp emptyDir, which holds the streamed artifact capped at the binary's 64Mi download limit plus the decrypted build tree): sizing it below would let the kubelet evict the pod mid-fetch on any artifact larger than the limit. |
resources.cpu | string | 50m | |
resources.ephemeralStorage | string | 320Mi | |
resources.memory | string | 256Mi | |
rollbackStore | object | (empty) | Optional rollback store for bit-exact, GC-independent rollbacks. |
rollbackStore.backend | string | none | none: rollback falls back to re-fetching the producer artifact. pvc: an RWX PersistentVolumeClaim (use RWX for HA replicas). s3: any S3-compatible bucket. |
rollbackStore.pvc | object | (empty) | |
rollbackStore.pvc.accessModes | array | (empty) | |
rollbackStore.pvc.mountPath | string | /var/lib/stageset/rollback | |
rollbackStore.pvc.size | string | 1Gi | |
rollbackStore.pvc.storageClass | string | (empty) | |
rollbackStore.s3 | object | (empty) | |
rollbackStore.s3.anonymous | boolean | false | |
rollbackStore.s3.bucket | string | (empty) | |
rollbackStore.s3.endpoint | string | (empty) | |
rollbackStore.s3.existingSecret | string | (empty) | Existing Secret carrying AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optionally AWS_SESSION_TOKEN. It is wired into the pod via envFrom.secretRef; the controller's AWS credential chain picks the keys up automatically. Leave empty to engage the IAM/IRSA discovery chain (AWS_* env vars, EKS web-identity, EC2 metadata). |
rollbackStore.s3.prefix | string | (empty) | |
rollbackStore.s3.region | string | (empty) | |
rollbackStore.s3.sse | string | s3 | Server-side encryption at rest for stored objects. The rollback store holds rendered Secret data, so this defaults on. s3 = SSE-S3 (bucket-managed key); kms = SSE-KMS with sseKmsKeyId; none only for a backend that cannot honor an SSE header. A rejected SSE write is non-fatal — it warns and skips the store write, the rollout still succeeds. |
rollbackStore.s3.sseKmsKeyId | string | (empty) | KMS key ARN/ID for sse: kms; empty uses the bucket's default KMS key. |
rollbackStore.s3.useSSL | boolean | true | |
securityContext | object | (empty) | Container-level securityContext (PSS restricted). runAsUser/runAsGroup are set above 10000 (kube-score requirement) for a static binary that needs no real user identity. |
securityContext.allowPrivilegeEscalation | boolean | false | |
securityContext.capabilities | object | (empty) | |
securityContext.capabilities.drop | array | (empty) | |
securityContext.readOnlyRootFilesystem | boolean | true | |
securityContext.runAsGroup | integer | 65532 | |
securityContext.runAsNonRoot | boolean | true | |
securityContext.runAsUser | integer | 65532 | |
securityContext.seccompProfile | object | (empty) | |
securityContext.seccompProfile.type | string | RuntimeDefault | |
serviceAccount | object | (empty) | Controller ServiceAccount. |
serviceAccount.annotations | object | (empty) | Extra annotations on the ServiceAccount — e.g. eks.amazonaws.com/role-arn for IRSA, so the controller's S3 rollback store assumes an IAM role instead of using static keys. |
serviceMesh | object | (empty) | Opt-in service-mesh L7 authorization + mTLS for the controller pod. Complementary to networkPolicy, NOT a replacement: networkPolicy is L3/L4 (which pods/IPs may open a connection), serviceMesh is L7/identity (which mesh identities may call which port). Both can be enabled at once and stack additively. Authorizes only the mesh-reachable ports — gate (Flagger) and metrics (Prometheus). The webhook (kube-apiserver) and health/probe (kubelet) ports are deliberately left out: those callers speak plain TLS / no mesh identity, and locking them to mesh principals would break admission and health probing. |
serviceMesh.defaultDeny | object | (empty) | Render a namespace-wide default-deny so every pod in the install namespace rejects unauthorized mesh traffic and the per-workload allows above are the only exceptions (zero-trust namespace). Istio renders an empty-spec AuthorizationPolicy (deny-all) scoped to the whole namespace, which sits at lower precedence than the workload ALLOW. Linkerd has no per-object deny-all; the namespace default is set via the config.linkerd.io/default-inbound-policy annotation, so this is stamped onto the chart-managed Namespace (requires namespace.create=true) — otherwise annotate the namespace out-of-band. Enable only when the controller owns its namespace; it also denies co-located workloads. |
serviceMesh.defaultDeny.enabled | boolean | false | |
serviceMesh.enabled | boolean | false | Opt-in. When enabled, the chart renders the selected engine's authorization + mTLS objects for the controller pod. Inert unless that mesh (Istio or Linkerd) is actually installed and the pod is injected. |
serviceMesh.engine | string | istio | Mesh engine to render for. istio emits security.istio.io AuthorizationPolicy (+ optional PeerAuthentication). linkerd emits policy.linkerd.io Server / AuthorizationPolicy / MeshTLSAuthentication objects. |
serviceMesh.gate | object | (empty) | Per-port allowed mesh identities. Each `from` entry is a source matcher: - principals: SPIFFE/mesh identities (Istio source.principals, e.g. cluster.local/ns/<ns>/sa/<sa>). For Linkerd these map to MeshTLSAuthentication identities (the proxy identity string, e.g. <sa>.<ns>.serviceaccount.identity.linkerd.cluster.local, or "*"). - namespaces: source namespaces (Istio source.namespaces). ISTIO-ONLY — Linkerd authenticates by workload identity, not by namespace, so this field is ignored on the linkerd engine. An EMPTY `from` list on a port means OPEN (allow any caller), mirroring networkPolicy's empty-`from`=open semantics. On Istio that is a port rule with no `from`; on Linkerd it is a MeshTLSAuthentication of identities: ["*"] (any authenticated meshed client). Mesh identities allowed to poll the Flagger stage-gate endpoint (ports.gate). Rendered only when gate.enabled. Empty = open. |
serviceMesh.gate.from | array | (empty) | |
serviceMesh.istio | object | (empty) | Istio engine raw passthrough. Entries are merged verbatim into the AuthorizationPolicy's spec.rules (security.istio.io/v1 rule schema). Use this to add rules the per-port `from` knobs above can't express — path/method matchers, `when` JWT-claim conditions, ipBlocks, etc. |
serviceMesh.istio.rules | array | (empty) | |
serviceMesh.linkerd | object | (empty) | Linkerd engine raw passthrough. Entries are appended verbatim as additional documents after the rendered Server / AuthorizationPolicy / MeshTLSAuthentication set — each entry must be a complete object (policy.linkerd.io AuthorizationPolicy, HTTPRoute, etc.). |
serviceMesh.linkerd.authorizations | array | (empty) | |
serviceMesh.mcp | object | (empty) | Mesh identities allowed to reach the MCP endpoint (ports.mcp). Rendered only when mcp.enabled. Empty = open. |
serviceMesh.mcp.from | array | (empty) | |
serviceMesh.metrics | object | (empty) | Mesh identities allowed to scrape the controller metrics port (ports.metrics). Empty = open. |
serviceMesh.metrics.from | array | (empty) | |
serviceMesh.mtls | string | (empty) | mTLS posture (Istio engine only). Empty defers to the mesh's own default (mesh-wide PeerAuthentication / MeshConfig). permissive renders a PeerAuthentication accepting both mTLS and plaintext. strict requires mTLS on the workload's ports EXCEPT the webhook + health ports, which get a port-level PERMISSIVE carve-out so the non-mesh kube-apiserver and kubelet still connect. Linkerd negotiates mTLS automatically between meshed pods, so this knob does not apply to the linkerd engine. |
tmpSizeLimit | string | 256Mi | Size limit for the writable /tmp emptyDir (artifact download + decrypted kustomize build scratch). Kept below resources.ephemeralStorage so an over-large artifact trips this with a clear message before kubelet eviction. |
tolerations | array | (empty) | Tolerations for scheduling. |
topologySpreadConstraints | array | (empty) | Topology spread constraints for scheduling. |
webhook | object | (empty) | Validating admission webhook for StageSet. |
webhook.certManager | object | (empty) | cert-manager mode: the issuer that signs the webhook Certificate. |
webhook.certManager.issuerRef | object | (empty) | |
webhook.certManager.issuerRef.kind | string | Issuer | |
webhook.certManager.issuerRef.name | string | (empty) | |
webhook.certMode | string | self-signed | TLS provisioning. self-signed (default) has the controller generate a CA + serving cert in-pod and patch the ValidatingWebhookConfiguration caBundle (HA-safe) — no cluster prerequisite, works out of the box. cert-manager renders a Certificate (cert mounted from the issued Secret) and needs cert-manager + a configured issuer. |
webhook.certValidity | string | 8760h | Validity of the self-signed serving cert; the renewer rotates at validity/3. |
webhook.enabled | boolean | true | |
webhook.failurePolicy | string | Fail | Failure policy for the ValidatingWebhookConfiguration. Fail is the safer default — a webhook outage blocks StageSet writes cluster-wide until the controller is back. Switch to Ignore only when you're willing to accept invalid StageSets reaching the reconciler. During a controller restart (rolling update, crash-loop, eviction) the webhook is briefly unreachable and Fail blocks every StageSet create/update for that window. If your CI/GitOps tooling can't tolerate that, scope the webhook with objectSelector / namespaceSelector to limit the blast radius, or set failurePolicy: Ignore. |
webhook.matchConditions | array | (empty) | matchConditions are CEL expressions evaluated server-side before the webhook is dialed. Lets operators short-circuit admission checks for specific StageSet shapes without a round trip. Requires Kubernetes 1.30+. |
webhook.namespaceSelector | object | (empty) | namespaceSelector restricts the webhook to Namespaces carrying the listed labels. A common pattern: opt-in per namespace via a label so the apiserver doesn't dial the webhook for unrelated namespaces during controller outages. Example: namespaceSelector: matchLabels: stages.metio.wtf/admission: enforce |
webhook.objectSelector | object | (empty) | objectSelector restricts the webhook to StageSets carrying the listed labels. Useful when only some StageSets are controller-managed (the rest live in Argo or kustomize). Example: objectSelector: matchLabels: stages.metio.wtf/managed: "true" |
webhook.sideEffects | string | None | Side-effect mode for the ValidatingWebhookConfiguration. |
webhook.timeoutSeconds | integer | 10 | Webhook call timeout in seconds. The apiserver aborts the admission call after this and applies failurePolicy. Keep low so a wedged controller fails fast instead of stalling every StageSet apply. |