Helm’s helm uninstall command is surprisingly destructive, deleting all Kubernetes resources associated with a release, even if those resources are still actively used by other applications.

Let’s see this in action. Imagine you have a deployment my-app-deployment and a service my-app-service managed by Helm.

# Example Helm Chart Snippet
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
  labels:
    app: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: nginx:latest
---
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
  labels:
    app: my-app
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

If you install this chart and then run helm uninstall my-release, both my-app-deployment and my-app-service will be gone. Now, what if another application, say another-app, also relies on my-app-service to communicate?

# Example Deployment for 'another-app'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: another-app-deployment
  labels:
    app: another-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: another-app
  template:
    metadata:
      labels:
        app: another-app
    spec:
      containers:
      - name: another-app
        image: ubuntu:latest
        command: ["sleep", "3600"]
        env:
        - name: MY_APP_SERVICE_HOST
          value: "my-app-service.default.svc.cluster.local" # Assumes default namespace

When helm uninstall my-release is executed, another-app-deployment will start failing because its my-app-service dependency has been deleted. This is a common scenario where shared infrastructure components, like databases, message queues, or utility services, are managed by a Helm release that is later uninstalled.

The core problem is that Helm, by default, views all resources it manages as belonging exclusively to that release. When the release is removed, Helm cleans up everything it thinks it "owns." Kubernetes itself doesn’t inherently track shared resource dependencies across different controllers or applications in a way that prevents deletion during a Helm uninstall.

To prevent this, you need to decouple the lifecycle of certain resources from the Helm release. The most straightforward method is to manually create these shared resources outside of your Helm chart.

Here’s how you’d do it:

  1. Identify Shared Resources: Before packaging your Helm chart, determine which Kubernetes resources are intended to be long-lived or shared. This might include Service objects for inter-application communication, PersistentVolumeClaims that should survive application updates, or Secrets and ConfigMaps used by multiple applications.

  2. Create Resources Manually: Instead of defining these resources within your Helm chart’s templates/ directory, create them as standalone YAML files and apply them directly to your cluster using kubectl apply -f <your-resource.yaml>.

    For instance, to create the my-app-service independently:

    # my-app-service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: my-app-service
      labels:
        app: my-app
        managed-by: manual # Optional: for identification
    spec:
      selector:
        app: my-app
      ports:
        - protocol: TCP
          port: 80
          targetPort: 80
    

    Apply it:

    kubectl apply -f my-app-service.yaml
    
  3. Modify Helm Chart: In your Helm chart, remove the YAML definitions for these manually created resources. Ensure they are no longer present in the templates/ directory.

  4. Configure Applications to Use Manual Resources:

    • For Deployments/Pods: If your Helm chart’s deployment needs to connect to a manually created service, you can either hardcode the service name (e.g., my-app-service.default.svc.cluster.local) in your application’s configuration or use Helm’s values.yaml to pass in the service name.
    • For other Helm Releases: If another Helm release needs to depend on this manually managed resource, it should be installed after the manual resource is created. The dependent Helm chart can then reference the resource by its name.
  5. Helm Release Lifecycle: When you helm uninstall the release that used to manage the shared resource, Helm will only delete the resources it still defines in its state. The manually created my-app-service will remain untouched, and other applications depending on it will continue to function.

Another advanced technique involves using Helm’s hooks and dependencies, but this can become complex. A simpler approach for truly shared infrastructure is to manage it outside of Helm entirely.

A more granular control mechanism is to leverage Kubernetes’ OwnerReferences. While Helm typically sets itself as the owner of resources, you can manipulate this. However, directly altering OwnerReferences from within Helm charts is discouraged and error-prone. A better approach is to use a "controller" that creates resources and sets appropriate OwnerReferences, allowing the resource to be garbage collected only when the controller itself is deleted. Helm can then manage the lifecycle of this controller. This is often seen in more sophisticated operators.

A common pitfall is forgetting to remove the resource definition from the Helm chart after you’ve created it manually. If the resource definition remains in the chart, Helm might try to manage it again during upgrades or re-installs, leading to unexpected behavior or conflicts.

The next logical step is to consider how to manage the lifecycle of the application that uses the shared resource, especially when that application is also managed by Helm.

Want structured learning?

Take the full Helm course →