criticalIntermediateSymptom-first#helm#upgrade#crd#resource-exists#managed-by

Helm upgrade fails — rendered manifests contain a resource that already exists

Error: rendered manifests contain a resource that already exists

Likely source:deploymentconfiguration

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'
READ

Check CRD ownership

Expected: NOT Helm-managed

Fix

kubectl get crd <CRD_NAME> -o yaml > /tmp/crd-backup.yaml
READ

Back up the existing CRD before adoption

kubectl annotate crd <CRD_NAME> meta.helm.sh/release-name=<RELEASE> meta.helm.sh/release-namespace=<NAMESPACE>
CHANGE

Annotate the CRD so Helm considers it owned

Expected: crd/<CRD_NAME> annotated

kubectl label crd <CRD_NAME> app.kubernetes.io/managed-by=helm
CHANGE

Add 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.

1
helm status <RELEASE> -n <NAMESPACE>
READ

Check the current release state and error details

2
kubectl get <RESOURCE> <NAME> -n <NAMESPACE> -o jsonpath='{.metadata.labels}'
READ

Inspect the ownership labels on the conflicting resource

3
kubectl annotate <RESOURCE> <NAME> -n <NAMESPACE> meta.helm.sh/release-name=<RELEASE> meta.helm.sh/release-namespace=<NAMESPACE> --overwrite
CHANGE

Adopt the resource into the Helm release via annotation

4
kubectl label <RESOURCE> <NAME> -n <NAMESPACE> app.kubernetes.io/managed-by=helm --overwrite
CHANGE

Add the managed-by label to complete adoption

5
helm upgrade <RELEASE> <CHART> -n <NAMESPACE> -f values.yaml
CHANGE

Retry 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.

Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.