Helm upgrade fails — rendered manifests contain a resource that already exists
Error: rendered manifests contain a resource that already exists
Focused incident mode — strips away everything but this runbook.
Symptoms
- Error: rendered manifests contain a resource that already exists
- cannot re-use a resource that is not owned by Helm
- Helm upgrade succeeds in dry-run but fails during apply
Never mix helm install with kubectl apply on the same resources. Use helm to adopt existing resources with annotations before upgrading.
Evidence to grab
Collect these artifacts before you change anything — they prove (or rule out) each cause.
Helm release status
helm status <RELEASE> -n <NAMESPACE>Resource owner labels
kubectl get <RESOURCE> <NAME> -n <NAMESPACE> -o jsonpath='{.metadata.labels}'
Diagnostic flow
Run the steps in order, marking each claim confirmed or refuted. The evidence you gather feeds the ranked causes below.
Guided decision tree is ready.
Steps with commands, expected output, and pass/fail branches.
Ranked root causes
Evidence checklist
- Helm CRD install is disabled (crds/ directory skipped on upgrade)
- CRD exists in the cluster with no Helm ownership labels
- kubectl apply or another operator created the CRD before the first helm install
Confirm before fixing
kubectl get crd <CRD_NAME> -o jsonpath='{.metadata.labels}' | grep -q 'managed-by' && echo 'Helm-managed' || echo 'NOT Helm-managed'Check CRD ownership
Expected: NOT Helm-managed
Fix
kubectl get crd <CRD_NAME> -o yaml > /tmp/crd-backup.yamlBack up the existing CRD before adoption
kubectl annotate crd <CRD_NAME> meta.helm.sh/release-name=<RELEASE> meta.helm.sh/release-namespace=<NAMESPACE>Annotate the CRD so Helm considers it owned
Expected: crd/<CRD_NAME> annotated
kubectl label crd <CRD_NAME> app.kubernetes.io/managed-by=helmAdd the managed-by label to complete adoption
Expected: crd/<CRD_NAME> labeled
Resolution runbook
Apply from top to bottom after the cause is confirmed. Review every command before running it — risk tags are shown on each.
helm status <RELEASE> -n <NAMESPACE>Check the current release state and error details
kubectl get <RESOURCE> <NAME> -n <NAMESPACE> -o jsonpath='{.metadata.labels}'Inspect the ownership labels on the conflicting resource
kubectl annotate <RESOURCE> <NAME> -n <NAMESPACE> meta.helm.sh/release-name=<RELEASE> meta.helm.sh/release-namespace=<NAMESPACE> --overwriteAdopt the resource into the Helm release via annotation
kubectl label <RESOURCE> <NAME> -n <NAMESPACE> app.kubernetes.io/managed-by=helm --overwriteAdd the managed-by label to complete adoption
helm upgrade <RELEASE> <CHART> -n <NAMESPACE> -f values.yamlRetry the upgrade after adoption
Expected: Release <RELEASE> has been upgraded
Prevention
- Always use Helm to manage all resources in a chart — never kubectl apply for the same objects.
- Pre-install CRDs separately (kubectl apply) before the first helm install if the chart uses crds/ directory.
- Use --skip-crds on upgrades if CRDs are already installed and managed outside Helm.
- Write a pre-upgrade check in CI that greps for 'resource already exists' to catch conflicts before deploy.
Related runbooks
Same stack, same failure family — worth a skim.
Pod stuck in CreateContainerConfigError or CrashLoopBackOff
Pod stuck in CreateContainerConfigError or CrashLoopBackOff with mount error
Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.