Installation¶
This guide covers installing kardinal-promoter in a Kubernetes cluster using Helm.
Prerequisites¶
| Requirement | Version | Notes |
|---|---|---|
| Kubernetes | ≥ 1.28 | kind, EKS, GKE, or any conformant cluster |
| kubectl | ≥ 1.28 | Matches your cluster version |
| Helm | ≥ 3.12 | brew install helm |
| GitHub token | — | Personal access token with repo scope |
Trying it out locally?
Use kind for a single-node local cluster. See the Quickstart for the fast path.
krocodile is bundled
kardinal-promoter bundles the krocodile Graph controller as part of its Helm chart. No separate krocodile install is required. A single helm install installs both controllers.
Install kardinal-promoter¶
1. Create the GitHub token secret¶
kardinal-promoter needs a GitHub personal access token to open and monitor pull requests. The token requires the repo scope (contents read/write, pull requests read/write).
kubectl create namespace kardinal-system
kubectl create secret generic github-token \
--namespace kardinal-system \
--from-literal=token=<your-github-token>
2. Install with Helm¶
helm install kardinal-promoter oci://ghcr.io/pnz1990/charts/kardinal-promoter \
--namespace kardinal-system \
--create-namespace \
--set github.secretRef.name=github-token
This single command installs:
- The kardinal-promoter controller in the
kardinal-systemnamespace - The krocodile Graph controller in the
kro-systemnamespace (unless--set krocodile.enabled=false)
Verify both controllers are running:
kubectl get pods -n kardinal-system
# NAME READY STATUS RESTARTS AGE
# kardinal-promoter-controller-6b6c8c8446-jc79g 1/1 Running 0 30s
kubectl get pods -n kro-system
# NAME READY STATUS RESTARTS AGE
# graph-controller-7d4b8f9f5-xk2pq 1/1 Running 0 30s
Bring your own krocodile¶
If you already run krocodile independently in your cluster, disable the bundled installation:
helm install kardinal-promoter oci://ghcr.io/pnz1990/charts/kardinal-promoter \
--namespace kardinal-system \
--create-namespace \
--set github.secretRef.name=github-token \
--set krocodile.enabled=false
Version compatibility
When running your own krocodile, ensure it is at a compatible commit. The required minimum is documented in hack/install-krocodile.sh. The bundled version is pinned per release and tested together.
Helm values reference¶
kardinal-promoter controller¶
| Key | Default | Description |
|---|---|---|
replicaCount | 1 | Number of controller replicas |
image.repository | ghcr.io/pnz1990/kardinal-promoter/controller | Controller image |
image.tag | Chart appVersion | Image tag |
image.pullPolicy | IfNotPresent | Pull policy |
logLevel | info | Log verbosity (debug, info, warn, error) |
leaderElect | true | Enable leader election (required for HA) |
github.token | "" | Direct token value (use secretRef in production) |
github.secretRef.name | "" | Name of existing Secret containing the token |
github.secretRef.key | token | Key in the Secret |
resources.limits.cpu | 500m | CPU limit |
resources.limits.memory | 128Mi | Memory limit |
resources.requests.cpu | 10m | CPU request |
resources.requests.memory | 64Mi | Memory request |
nodeSelector | {} | Node selector |
tolerations | [] | Pod tolerations |
affinity | {} | Pod affinity |
validatingAdmissionPolicy.enabled | true | Deploy ValidatingAdmissionPolicy (requires Kubernetes ≥ 1.28) |
krocodile Graph controller¶
| Key | Default | Description |
|---|---|---|
krocodile.enabled | true | Install bundled krocodile controller |
krocodile.image.repository | ghcr.io/pnz1990/kardinal-promoter/krocodile | krocodile image |
krocodile.image.tag | krocodile.pinnedCommit | Image tag (defaults to pinned commit SHA) |
krocodile.pinnedCommit | See Chart.yaml annotations | Source commit bundled with this release |
krocodile.namespace | kro-system | Namespace for krocodile controller |
krocodile.replicaCount | 1 | Number of krocodile replicas |
krocodile.resources.limits.memory | 512Mi | Memory limit |
Accessing the UI¶
The kardinal controller serves an embedded web UI at port 8082 (configurable via --ui-listen-address).
In-cluster access (recommended): kubectl port-forward¶
The supported access method for in-cluster deployments without Ingress is kubectl port-forward:
Then open http://localhost:8082/ui/ in your browser.
Why port-forward?
Port-forwarding routes traffic through the Kubernetes API server over a secure tunnel — no Ingress or LoadBalancer needed. It is the recommended approach for platform engineers accessing the UI from their workstation.
Avoid accessing the UI over plain HTTP from a remote address
If you expose port 8082 directly (e.g. via NodePort) without TLS, the UI will display a security warning. Use port-forward from localhost instead, or configure TLS with --tls-cert-file / --tls-key-file.
With TLS (production)¶
If you configure TLS via --tls-cert-file / --tls-key-file (or the Helm values controller.tlsCertFile / controller.tlsKeyFile), the UI is served over HTTPS and no browser warning is shown. See the Security guide for a cert-manager example.
Upgrade¶
helm upgrade kardinal-promoter oci://ghcr.io/pnz1990/charts/kardinal-promoter \
--namespace kardinal-system \
--reuse-values
Graceful shutdown¶
The controller handles SIGTERM gracefully: it stops accepting new reconcile requests and allows in-flight reconcile loops up to 30 seconds to complete before exiting. This prevents mid-step interruptions during rolling updates or node evictions — for example, a git-push that was 5 seconds from finishing will complete rather than leaving the PromotionStep in an inconsistent state.
The Helm chart sets terminationGracePeriodSeconds: 60 (double the shutdown timeout) so Kubernetes sends SIGKILL only after the controller has had a full 30 seconds to drain.
To adjust the timeout:
This upgrades both the kardinal-promoter controller and the bundled krocodile controller to the versions pinned in the new chart version.
Note
kardinal-promoter is backwards-compatible across patch versions. Minor version upgrades may introduce new CRD fields — apply updated CRDs with kubectl apply -f config/crd/bases/ before upgrading the controller.
Uninstall¶
helm uninstall kardinal-promoter -n kardinal-system
# Optional: remove kardinal CRDs (deletes all Pipelines, Bundles, PolicyGates, etc.)
kubectl delete crd \
pipelines.kardinal.io \
bundles.kardinal.io \
promotionsteps.kardinal.io \
policygates.kardinal.io \
prstatuses.kardinal.io \
rollbackpolicies.kardinal.io
# Optional: remove krocodile CRDs (only if you don't use krocodile elsewhere)
kubectl delete crd \
graphs.experimental.kro.run \
graphrevisions.experimental.kro.run
# Optional: remove krocodile namespace
kubectl delete namespace kro-system
RBAC requirements¶
The kardinal-promoter controller's ServiceAccount requires:
| Resource | Verbs |
|---|---|
pipelines, bundles, promotionsteps, policygates, prstatuses, rollbackpolicies | get, list, watch, create, update, patch, delete |
graphs.experimental.kro.run | get, list, watch, create, update, patch, delete |
deployments, services, pods | get, list, watch |
secrets | get (GitHub token secret only) |
events | create, patch |
configmaps | get, create, update (leader election + version ConfigMap) |
The krocodile controller's ServiceAccount requires full cluster-level access to manage arbitrary resources as directed by Graph specs. This is inherent to its design as a general-purpose DAG engine — it applies resources of any type that appear in Graph node templates.
Both ClusterRole and ClusterRoleBinding resources are created automatically by the Helm chart.
Next steps¶
- Quickstart — apply your first Pipeline and promote a Bundle
- Concepts — understand Pipelines, Bundles, PolicyGates
- Policy Gates — write CEL expressions for promotion policies
- Troubleshooting — diagnose installation issues with
kardinal doctor