Install with Helm¶
Two charts are published to GHCR as OCI artifacts on every release: patchy installs the whole stack — the
patchy.bitwisemedia.uk CRDs, five controller Deployments with their RBAC and ConfigMaps, the two Services, the agent
namespace, and the baseline network policies — and patchy-config installs the Integration/Forge custom resources
that switch the pipeline on, as a second release once the CRDs exist.
Create the namespaces¶
The chart creates the agent namespace (patchy-agents) itself, but the release namespace is yours. Both must carry the
restricted Pod Security Standard — the chart labels the agent namespace; label the release namespace yourself:
kubectl create namespace patchy
kubectl label namespace patchy \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
Create the secrets¶
Patchy references two Secrets but refuses to own them — create them out of band (SOPS, external-secrets, or plain
kubectl for a first run). One lives in the release namespace, one in the agent namespace:
# The GitHub credential + webhook secret (all three values from the previous page)
kubectl -n patchy create secret generic patchy-github \
--from-literal=appID=123456 \
--from-file=privateKey=./patchy.private-key.pem \
--from-literal=webhookSecret="$WEBHOOK_SECRET"
# The claude model credential — in the RELEASE namespace, where the egress
# credential broker (the only thing that ever holds it) runs. It never
# enters an agent pod.
kubectl -n patchy create secret generic patchy-anthropic \
--from-literal=api-key="$ANTHROPIC_API_KEY"
No Anthropic API key? A Claude subscription works too: mint a long-lived OAuth token with
claude setup-token, store it in the same secret, and set
egressBroker.anthropicAuth: token (Helm) or PATCHY_ANTHROPIC_AUTH=token (kustomize) so the broker sends it as a
bearer rather than an API key:
kubectl -n patchy create secret generic patchy-anthropic \
--from-literal=api-key="$(claude setup-token)"
Running claude against Amazon Bedrock, GCP Vertex AI, or Microsoft Foundry instead needs no Anthropic Secret at all — see the provider recipes.
To let the investigation choose OpenAI models for remediation, enable the codex runner
(agent.runners.codex.enabled: true / add codex to PATCHY_HARNESSES) and create its credential:
kubectl -n patchy-agents create secret generic patchy-openai --from-literal=api-key="$OPENAI_API_KEY"
The copilot runner (agent.runners.copilot.enabled: true / add copilot to PATCHY_HARNESSES) is the alternative to
both: it brokers Anthropic and OpenAI models, so one credential covers every model in the registry. That credential
is a GitHub token rather than a model API key, which is why it is off by default — scope it to Copilot with no
repository permissions (the CLI rejects classic PATs; use a fine-grained token):
kubectl -n patchy-agents create secret generic patchy-copilot --from-literal=token="$COPILOT_GITHUB_TOKEN"
| Secret | Namespace | Keys | Consumed by |
|---|---|---|---|
patchy-github |
patchy |
appID + privateKey (or token), webhookSecret |
The Integration/Forge CRs' spec.secretRef — read on demand through the API, never mounted |
patchy-anthropic |
patchy |
api-key |
The egress broker only — never an agent pod (an API key, or a claude setup-token OAuth token with egressBroker.anthropicAuth: token) |
patchy-openai |
patchy-agents |
api-key |
Codex runner Job pods (OPENAI_API_KEY) — only when the codex runner is enabled |
patchy-copilot |
patchy-agents |
token |
Copilot runner Job pods (COPILOT_GITHUB_TOKEN) — only when the copilot runner is enabled |
A token key (a personal access token) is the dev-only fallback and wins over App auth when set. One GitHub Secret may
serve both CRs, or you can split read and write identities across two GitHub Apps and two Secrets.
Each enabled non-brokered harness needs its credential
A codex or copilot harness is enabled only when its credential Secret exists in the agent namespace (the
controllers validate this at startup and refuse to start otherwise); the Job builder wires that credential into the
pod via a secretKeyRef. The claude harness is different: it is enabled by configuration alone — its credential
lives with the egress broker, whose readiness probe is where a missing key surfaces. The fake harness (dev only)
needs no credential.
There is deliberately no GitHub credential in the agent namespace — not even a per-Job one. The repository arrives as a digest-verified tarball from the source-controller's in-cluster artifact server. See the isolation model.
Install the chart¶
The patchy chart installs the stack — CRDs, controllers, agent sandbox — and nothing pipeline-specific:
# values.yaml
agent:
networkPolicy:
# FQDN egress for the agent sandbox. `auto` (the default) detects the
# dialect the cluster enforces; pin cilium / gke / istio to be explicit.
mode: auto
helm install patchy oci://ghcr.io/bitwise-media-group/patchy/charts/patchy \
--version <X.Y.Z> --namespace patchy -f values.yaml
The chart's appVersion is stamped 1:1 with each release, and the default image tag is derived from it — installing
chart X.Y.Z runs images vX.Y.Z. The rendered NOTES.txt recaps the webhook URL and the Secrets it expects. The full
values surface is on the Helm chart page.
Switch the pipeline on: the patchy-config chart¶
The controllers idle until two custom resources exist: an Integration (where
findings come from, where the tracking issues go, webhook validation) and a
Forge (how repositories are fetched and pushed). They ship as the separate
patchy-config chart — separate because Helm validates every manifest against the API server before applying anything,
so the CRs cannot install in the same first release as the CRDs they depend on. Install it after the patchy chart,
into the same namespace:
# config-values.yaml
integrations:
- name: github
spec:
provider: github
secretRef:
name: patchy-github
interval: 10m
github:
issues:
enabled: true
approveComment: /approve
codeScanningAlerts:
enabled: true
forges:
- name: github
spec:
provider: github
secretRef:
name: patchy-github
interval: 10m
helm install patchy-config oci://ghcr.io/bitwise-media-group/patchy/charts/patchy-config \
--version <X.Y.Z> --namespace patchy -f config-values.yaml
Each entry's spec is validated client-side by the chart's values schema (generated from the CRDs, so a typo'd field
fails the install before anything is applied) and again server-side by the CRD —
deploy/kustomize/base/crs.example.yaml
is the full field walkthrough (GHES base URLs, org allowlists, repository regexes). Prefer applying the CRs yourself?
Skip this chart and kubectl apply the same objects after the install.
Expose the webhook¶
Expose the integration-controller — the single internet-facing component — and point the GitHub App's webhook URL at
https://<webhook.host>/github/webhooks:
webhook:
host: patchy.example.com
ingress: # or httpRoute — see the webhook exposure page
enabled: true
className: nginx
tls:
- secretName: patchy-webhook-tls
hosts: [patchy.example.com]
Flavours, TLS, and the EKS / AKS / GKE notes live in Deployment → Webhook exposure. The
other controllers stay cluster-internal; every controller serves /healthz and /readyz probes on port 8081.
The kustomize alternative¶
The same stack renders from deploy/kustomize if Helm isn't your tool:
kubectl apply -k deploy/kustomize/overlays/dev # kind/colima: throwaway secrets, fast loops, fake harness
kubectl apply -k deploy/kustomize/overlays/prod # digest-pinned images + Cilium FQDN egress
Bring the same two Secrets and your Integration/Forge resources; the base and overlays are covered in
Deployment → Kustomize.
Verify provenance (optional)¶
Every chart version and container image carries a GitHub build-provenance attestation: