Kubernetes manifests all look correct. A Deployment without a livenessProbe still deploys. An RBAC Role with verbs: ["*"] still applies. A Secret committed as plaintext data: still works in the cluster — right up until a security review or an incident finds it. None of these fail at kubectl apply time, which is exactly the problem for Claude Code without a CLAUDE.md: every option that compiles and applies looks equally valid, so the agent reaches for whatever’s most common in its training data instead of your team’s actual conventions.
We looked at the CLAUDE.md guidance already out there for Kubernetes, and most of it stops at manifests, RBAC, and kustomize overlays — genuinely useful, but it’s the first 60% of the problem. The parts that cause the most rework once a cluster is running real workloads are the ones that get skipped: how Claude Code should author a Helm chart’s values.yaml and _helpers.tpl (not just consume one), what a GitOps repo structure means for CLAUDE.md when Claude Code is editing manifests it doesn’t directly kubectl apply, and how to write policy guardrails (OPA/Gatekeeper, Kyverno) into the rules file instead of hoping code review catches them. This guide covers all three, plus what we found wrong across four real Kubernetes rule files in our own gallery.
Why Kubernetes Breaks Generic AI Coding Rules
Kubernetes has more ways to describe the same working deployment than almost any other stack, and none of the wrong choices produce a build error.
Resource limits are optional until the node runs out of memory. A Deployment with no resources.requests or resources.limits schedules fine, runs fine in a demo, and then gets OOM-killed or starves its neighbors the first time real traffic hits it — by which point the manifest has long since been merged.
RBAC that “just works” is usually over-permissioned. verbs: ["*"] on a Role passes every functional test, because it can do everything a narrower Role can. The failure isn’t a bug report, it’s a security review months later asking why a metrics-exporter ServiceAccount can delete Secrets.
Helm and kustomize solve overlapping problems differently, and an agent without an explicit rule will reach for whichever one shows up more in its training data — usually Helm, even for a project that standardized on kustomize overlays specifically to avoid templating logic in YAML.
GitOps changes what “applying a change” means. In a repo synced by Argo CD or Flux, Claude Code editing a manifest and running kubectl apply directly is actively wrong — it fights the GitOps controller, which reconciles the cluster back to whatever’s in Git within minutes. Nothing about the YAML syntax signals that.
Complete CLAUDE.md Template for Kubernetes Projects
This targets a Helm + Argo CD setup on Kubernetes 1.33+, which is the most common combination we see across the gallery’s DevOps rule files. Swap the GitOps controller and chart tooling sections if your project uses Flux or plain kustomize instead.
# Kubernetes Project: [ProjectName]
## Cluster & Tooling
- Kubernetes 1.33+, EKS managed control plane
- Chart tooling: Helm 3.16+ — this project templates its own charts, does not just consume vendor charts
- GitOps: Argo CD, app-of-apps pattern — never `kubectl apply` directly against `prod`, edit Git and let Argo CD reconcile
- Local dev/testing: kind or k3d, never test manifests against `prod` context
## API Versions (avoid deprecated / removed)
- `apps/v1` for Deployments, StatefulSets, DaemonSets — `extensions/v1beta1` was removed in 1.22, do not suggest it
- `networking.k8s.io/v1` for Ingress and NetworkPolicy
- `autoscaling/v2` for HorizontalPodAutoscaler — not `autoscaling/v1`
- `policy/v1` for PodDisruptionBudget
## Manifest Conventions (HARD)
- Every container sets `resources.requests` and `resources.limits` — CPU in millicores (`250m`), memory in Mi/Gi
- Every Deployment has `livenessProbe` and `readinessProbe`; add `startupProbe` for slow-starting containers
- No `kind: Secret` with a plaintext `data:` block committed to the repo — use ExternalSecret backed by the cluster's secrets manager
- `securityContext.runAsNonRoot: true` at the Pod level unless explicitly justified in a comment
- Pin image tags — never `:latest` in `prod` or `staging` overlays
## Helm Chart Authoring (this project templates charts, not just installs them)
- `values.yaml` is the single source of tunable config — never hardcode an environment-specific value inside a template
- Every value referenced in a template has a default in `values.yaml`, even if the default is empty/null
- Shared template logic goes in `_helpers.tpl` as named templates (`define`/`include`) — never copy-paste template blocks across `templates/*.yaml`
- Use `{{- with }}` / `{{- if }}` with `-` trim markers to avoid blank-line YAML output
- `Chart.yaml` version bumps on every change that alters rendered output, even patch-level
## GitOps (Argo CD)
- All manifest changes go through Git — Claude Code never runs `kubectl apply`/`kubectl edit` against clusters this repo manages
- Repo structure: `apps/<app>/base` (kustomize base or Helm chart) + `apps/<app>/overlays/<env>` — environment differences live in overlays, not the base
- App-of-apps root (`argocd-apps/`) only changes when adding/removing an application, not for routine config changes
- Sync waves (`argocd.argoproj.io/sync-wave`) matter for ordering — do not reorder resources without checking dependency implications
## Policy Guardrails (OPA/Gatekeeper or Kyverno — confirm which this repo uses)
- Do not write a manifest that would violate an existing `ConstraintTemplate`/`ClusterPolicy` — check `policy/` before adding a new resource type
- New policies require both an enforce-mode test and a dry-run/audit period — never ship a new constraint straight to `enforce`
- Namespace-scoped exemptions go in the policy's `match.excludedNamespaces`, not as an inline annotation on the workload
## RBAC & Namespaces
- Least privilege by default — list explicit `verbs`, never `["*"]`
- One ServiceAccount per application, not shared across unrelated workloads
- New namespace requires a `ResourceQuota` and `NetworkPolicy` (default-deny ingress) in the same change
## kubectl / Debugging
- Always pass `--context` explicitly — never rely on whatever context happens to be current
- Read-only commands (`get`, `describe`, `logs`) freely; destructive commands (`delete`, `apply` outside dev) require explicit confirmation
- `kubectl exec` into a running pod is a debugging tool, not a deployment mechanism — changes made this way don't survive a GitOps reconcile
Helm Chart Authoring: The Part Generic Guides Skip
Most Kubernetes + Claude Code guidance treats Helm as something you install from, not something you write. That’s fine if your project only consumes vendor charts — it falls apart the moment your team maintains its own chart, because the failure modes are template-specific, not manifest-specific.
# Bad — hardcoded value inside the template, no values.yaml entry
# templates/deployment.yaml
spec:
containers:
- name: api
image: myregistry/api:2.4.1
resources:
limits:
memory: 512Mi
# Good — every tunable value comes from values.yaml, defaults declared explicitly
# templates/deployment.yaml
spec:
containers:
- name: api
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
resources:
{{- toYaml .Values.resources | nindent 10 }}
# values.yaml
image:
repository: myregistry/api
tag: "" # falls back to Chart.AppVersion — see template above
resources:
limits:
memory: 512Mi
cpu: 500m
requests:
memory: 256Mi
cpu: 100m
The bad version isn’t wrong in the sense of failing helm template — it renders fine. It’s wrong because the next environment that needs a different memory limit can’t get one without editing the template itself, which defeats the entire point of a chart. State explicitly in CLAUDE.md that values.yaml is the only place environment-specific numbers live, and that shared logic — a label block, an annotation set, a common env-var list — belongs in _helpers.tpl as a named template, not duplicated across templates/deployment.yaml and templates/statefulset.yaml.
GitOps Rules: Why “kubectl apply” Is the Wrong Answer in a Synced Repo
This is the gap we saw most consistently: existing Kubernetes CLAUDE.md guidance covers manifest correctness in isolation, as if every change gets applied directly to the cluster. In an Argo CD– or Flux-managed repo, that assumption is actively harmful — Claude Code running kubectl apply against a GitOps-managed namespace either gets silently reverted on the next sync, or worse, causes a drift alert that someone has to investigate later.
## GitOps (add to CLAUDE.md if this repo is synced by Argo CD/Flux)
- This repo is the source of truth. Cluster state is reconciled FROM this repo, not the other way around.
- Never suggest `kubectl apply -f` or `kubectl edit` for anything under `apps/` — the change goes in Git, Argo CD applies it.
- `kubectl diff` and `kubectl get -o yaml` are fine for inspecting current state — read-only only.
- If a manifest change needs to bypass the normal sync interval, say so explicitly and name the reason (e.g., "needs immediate rollback") — don't silently reach for a direct apply.
The app-of-apps pattern adds one more wrinkle worth stating explicitly: the root application (the one that points at all the other Argo CD Application manifests) changes far less often than individual app configs, and touching it incorrectly can cascade to every application it manages. If your CLAUDE.md doesn’t distinguish “this is a routine config change to one app’s overlay” from “this touches the app-of-apps root,” Claude Code has no way to know which one it’s looking at.
Policy Guardrails: OPA/Gatekeeper and Kyverno in CLAUDE.md
Policy engines are usually treated as a CI-time or admission-time concern — something that rejects a bad manifest after the fact, rather than something the coding rules file should know about upfront. That’s a missed opportunity: if CLAUDE.md names the active constraints, Claude Code can avoid violations before they ever hit a pull request.
## Policy Engine: Kyverno (adjust for OPA/Gatekeeper)
- Active ClusterPolicies live in `policy/` — read them before adding a new resource kind or changing a Pod spec
- Known hard constraints in this cluster:
- `require-resource-limits` — every container must set CPU/memory limits
- `disallow-latest-tag` — image tags must not be `latest` or empty
- `require-run-as-nonroot` — securityContext.runAsNonRoot must be true
- A new policy always ships in `audit` mode first, with a documented review date before switching to `enforce`
- Do not add `excludedNamespaces` to bypass a policy for convenience — that's a review decision, not a default
Naming the specific constraints — not just “there’s a policy engine” — is what makes this actionable. A generic note like “follow cluster policies” gives Claude Code nothing to check against; a list of the actual ClusterPolicy/ConstraintTemplate names lets it catch a violation the same way it’d catch a lint error.
What Real-World Kubernetes Rule Files Get Wrong
We compared four Kubernetes-focused rule files across our gallery — a Cursor DevOps + Helm rule, a Docker + Kubernetes containerization rule, a broader DevOps + Azure Pipelines rule, and a Claude Code–native skill pack — and the gaps were consistent across all four:
- Kubernetes + Helm covers resource requests/limits, probes, and security context in solid detail, but stops at manifest authoring — no mention of chart templating conventions like
_helpers.tplorvalues.yamlstructure. - Docker + Kubernetes Containerization is strong on the container-build side (multi-stage builds, non-root users, layer caching) but treats the Kubernetes side as “apply the manifest,” with nothing about GitOps or policy enforcement.
- DevOps + Kubernetes + Azure Pipelines is CI/CD-focused and genuinely useful for the pipeline side, but its Kubernetes guidance is generic IaC advice — it doesn’t distinguish Kubernetes-specific failure modes (API version deprecation, probe misconfiguration) from Terraform or Ansible ones.
- kstack is a Claude Code–native skill pack rather than a rules file, and it’s scoped to shell/installer conventions, not cluster resource conventions — a useful example of the skill-pack format, but not a substitute for a project-level Kubernetes
CLAUDE.md.
None of the four mention GitOps reconciliation behavior, and none name specific policy-engine constraints. That’s the gap this template fills — not because the existing rules are wrong, but because they were each written for a narrower slice of the problem than “a team running Helm charts through Argo CD with policy enforcement in front of it.”
AGENTS.md Compatible Version
# AGENTS.md — Kubernetes Project
## Commands
- render chart: `helm template . -f values.yaml`
- lint chart: `helm lint .`
- diff against live cluster (read-only): `kubectl diff -f <manifest>`
- local cluster: `kind create cluster` / `k3d cluster create`
## Critical Rules
1. Every container sets resources.requests and resources.limits — no exceptions
2. Every Deployment has livenessProbe and readinessProbe
3. No plaintext Secret data: blocks — use ExternalSecret only
4. GitOps-managed repos: never kubectl apply directly, edit Git and let Argo CD/Flux reconcile
5. values.yaml is the only place environment-specific config lives in a Helm chart — never hardcode inside templates
6. RBAC: explicit verbs only, never verbs: ["*"]
7. Image tags are pinned — never :latest in staging or prod
8. Check policy/ for active ClusterPolicy/ConstraintTemplate constraints before adding a new resource kind
## Testing
- `helm template` + `kubeconform` (or `kubectl apply --dry-run=server`) before any manifest change is considered done
- Policy dry-run (`kyverno test` or `conftest test`) against active constraints if the repo has a policy/ directory
Common AI + Kubernetes Mistakes to Watch For
Suggesting kubectl apply in a GitOps-managed repo. The single most common mistake once a project moves to Argo CD or Flux — it’s the default instinct for “make this change happen,” and it’s specifically the wrong instinct here.
Reaching for autoscaling/v1 instead of v2 for HPA. v1 only supports CPU-based scaling; v2 supports memory and custom metrics. An agent without a stated API version list will often default to whichever appears more often in older training data.
Copy-pasting a label block across three Helm templates instead of defining it once in _helpers.tpl. Works fine until the label scheme changes and now three files need the same edit instead of one.
Adding excludedNamespaces to a policy constraint to unblock a failing deployment, instead of fixing the manifest or explicitly flagging that the policy itself needs review. It’s the path of least resistance and it quietly erodes the guarantee the policy existed to provide.
Mixing kustomize overlays and Helm values for the same concern. If a project standardized on one, an agent that reaches for the other because it’s more familiar with that syntax produces two competing sources of truth for the same environment difference.
Resource limits, probes, and RBAC verbs are the baseline any Kubernetes CLAUDE.md should cover — but they’re also the part every existing guide already covers well. The gap is upstream and downstream of the manifest itself: how the chart that generates it gets authored, how the change actually reaches the cluster once GitOps is in the loop, and what policy constraints silently reject it if the manifest gets those first two wrong.
Browse the full Kubernetes + Helm rule or the Docker + Kubernetes containerization rule in our gallery, or see how a Terraform-based IaC CLAUDE.md compares if your project pairs Kubernetes with cloud infrastructure provisioning.
FAQ
Should CLAUDE.md tell Claude Code to run kubectl apply directly, or always go through Git?
If the cluster is managed by Argo CD or Flux, always through Git — a direct kubectl apply either gets reverted on the next reconcile or causes a drift alert. If there’s no GitOps controller in front of the cluster, direct kubectl apply for non-prod environments is fine, but say so explicitly in CLAUDE.md so Claude Code doesn’t assume GitOps is in play when it isn’t.
Does this template work with kustomize instead of Helm?
The manifest conventions, RBAC, GitOps, and policy sections apply either way. Swap the Helm Chart Authoring section for kustomize-specific guidance (base/overlay structure, strategic merge vs. JSON patches, commonLabels vs. per-resource labels) — the underlying principle of “environment differences live in the overlay, not the base” is the same idea kustomize was built around.
How do I know which policy engine my cluster actually uses?
Check for a policy/ directory with ClusterPolicy resources (Kyverno) or ConstraintTemplate/Constraint resources (OPA/Gatekeeper) in the repo, or run kubectl get clusterpolicies / kubectl get constrainttemplates against the cluster. Name whichever one applies explicitly in CLAUDE.md — the two have different resource kinds and Claude Code can’t infer which is active from the manifest style alone.
Is a single root CLAUDE.md enough for a multi-app GitOps monorepo?
For the shared conventions (API versions, RBAC defaults, policy engine name) yes — but if individual apps have genuinely different owners or tech stacks, a per-app CLAUDE.md under apps/<app>/ that inherits the root file and states only what differs keeps each file short enough to be useful, the same pattern that works for large Helm chart repos or module-based monorepos in other stacks.