Contributing¶
This is a GitOps monorepo: declare what you want in Git, open a pull request, and let Flux reconcile it. Start with the Architecture overview for the big picture, then follow the conventions below.
Agent tooling¶
Tool-agnostic agent instructions and skills live under .agents/ so any assistant (Codex, Copilot,
Cursor, Claude Code) can apply the same conventions:
.agents/instructions/sorting.instructions.md: YAML sorting conventions (alphabetical defaults plus app-template-specific ordering). Apply this when asked to sort YAML..agents/skills/add-app/SKILL.md: a skill that scaffolds a new app-template application following the conventions below. Agent tools that read.agents/skills/can invoke it directly.
AGENTS.md at the repository root is the canonical, tool-agnostic conventions guide. The local
CLAUDE.md imports it via an @AGENTS.md include and adds Claude-Code-specific specifics on top
(kubeconfig usage, just command reference, Flux reconciliation steps).
Adding a new app¶
Use the add-app skill to scaffold, or follow the shape by hand. Every app lives at
kubernetes/apps/<namespace>/<app>/ and follows the same pattern:
ks.yamlis the Flux entry point:- Use YAML anchors (
&app,&namespace,*app) for DRY references and settargetNamespace: *namespace. - Declare any
components(volsync,alerts,homepage) here, along with theirpostBuild.substitutevalues (APP: *app,VOLSYNC_CAPACITY). Do not duplicate components intoapp/kustomization.yaml. (Gatus monitoring is automatic: the gatus-sidecar chart auto-discovers HTTPRoutes, so there is no per-appgatus/guardedcomponent anymore.) - Add
dependsOnonepassword-connectinexternal-secretsif the app uses an ExternalSecret. - Inside
app/: - Add a per-app
ocirepository.yamlpointing atoci://ghcr.io/bjw-s-labs/helm/app-template; the HelmRelease references it viaspec.chartRef.kind: OCIRepository,name: <app>. There is no sharedapp-templatesource. Non-app-template charts may use aHelmRepositoryinstead. - Order the HelmRelease
specasinterval→chartRef→dependsOn→values; most HRs omitinstall/upgradeand inherit them from the root Kustomization. - Prefer an inline
route:in the HelmRelease values on theenvoy-internal/envoy-externallistener (namespacenetwork) over a standalonehttproute.yaml. Hosts are${APP}.${SECRET_DOMAIN}and${APP}.${SECRET_INTERNAL_DOMAIN}. - Add a per-app
externalsecret.yamlif it needs secrets (see Secrets). - Register the app in the namespace's
kustomization.yaml, keeping the list alphabetical, and reference the namespace's components (typicallyglobal-vars+alerts).
House rules to respect¶
ConfigMapresources must setmetadata.namespaceexplicitly. Checkov (CKV_K8S_21) scans raw YAML before Flux appliestargetNamespaceand flagsdefault.- Escape any literal
${VAR}you want preserved as$${VAR}; Flux substitutes unescaped${VAR}againstcluster-secrets/cluster-settings, and undefined vars become empty strings. - GPU workloads set
runtimeClassName: nvidia. - Keep the repository public-safe: no LAN IPs, node or device hostnames, MACs, or internal hostnames in Git. The security-scans CI guard enforces this. See Secrets.
Validate before pushing¶
PR renders and diffs are posted by the in-cluster Konflate as a native commit status plus a PR comment
(there is no GitHub Actions render workflow). GitHub Actions still run security scans (Checkov/Trivy) and
super-linter. Mirror the render locally first with flate, preferably via the just wrappers defined in
kubernetes/mod.just (the raw flate invocations underneath are shown for reference):
# Render a single app's HelmRelease / Kustomization
just kube flate-build-hr <namespace> <app>
just kube flate-build-ks <namespace> <app>
# Test all Kustomizations + HelmReleases
just kube flate-test
# Underlying flate invocations
flate build hr <app> -n <namespace> --path kubernetes/flux/cluster --allow-missing-secrets
flate test all --path kubernetes/flux/cluster --allow-missing-secrets
Pre-commit hooks (lefthook)¶
Lefthook runs automatically on git commit. Per .lefthook.toml, it formats YAML (yamlfmt,
prettier) and JSON/JSON5 (prettier), and lints shell scripts (shellcheck), GitHub Actions
workflows (actionlint, zizmor). It also mirrors several super-linter checks in report-only
mode (yamllint, codespell, markdownlint, editorconfig-checker), scoped to staged files so
their findings surface at commit time instead of in CI. .lefthook.toml is the source of truth for
the exact command/glob/exclude for each hook.
Where AI planning artifacts go¶
AI planning artifacts (specs, plans, scratch notes from superpowers-style workflows) live in the
gitignored docs/superpowers/ directory. They are not committed. Durable knowledge that future
maintainers and agents need is promoted into this knowledge base instead, so the published site stays
the single source of truth.