Helm can query Kubernetes to find existing resources and use that information in your templates.

Here’s a simple example: Suppose you have a ConfigMap named my-config in the default namespace and you want to use a specific key from it, app.properties, in your Deployment’s environment variables.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: nginx:latest
        env:
        - name: APP_CONFIG
          valueFrom:
            configMapKeyRef:
              name: my-config
              key: app.properties

This is standard Kubernetes. But what if you don’t know the ConfigMap name or the key beforehand? What if it’s defined in a different Helm release, or even outside of Helm? This is where Helm’s lookup capabilities shine.

Helm allows you to access information about any Kubernetes resource that is accessible to your kubectl context, regardless of whether it was deployed by Helm or not. This is achieved using the lookup function.

The lookup function takes three arguments:

  1. apiVersion: The API version of the resource (e.g., "v1", "apps/v1").
  2. kind: The kind of the resource (e.g., "ConfigMap", "Service", "Secret").
  3. name: The name of the resource.
  4. namespace (optional): The namespace of the resource. If omitted, it defaults to the release’s namespace.

Let’s rewrite the Deployment example to dynamically fetch the app.properties value from a ConfigMap named my-app-config in the default namespace.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: nginx:latest
        env:
        - name: APP_CONFIG

          value: {{ lookup "v1" "ConfigMap" "my-app-config" "default" | fromJson | get "data.app.properties" }}

Here’s what’s happening:

  • lookup "v1" "ConfigMap" "my-app-config" "default": This queries the Kubernetes API for a ConfigMap named my-app-config in the default namespace. If found, it returns a JSON representation of that ConfigMap object.
  • | fromJson: This Helm template function parses the JSON string returned by lookup into a Go map.
  • | get "data.app.properties": This is a chain of get calls. It first gets the data field from the map (which is itself a map containing the ConfigMap’s data), and then it gets the app.properties key from that inner map.

This approach is incredibly powerful because it decouples your Helm charts from needing to know exactly how dependent resources are provisioned. You can reference existing Secrets for database passwords, existing Services for internal endpoints, or even ConfigMaps that are managed by a completely different process or Helm release.

Consider a scenario where you’re deploying an application that needs to connect to a database. The database might already exist, provisioned by a separate Helm chart or a database-as-a-service offering. You can use lookup to get the database service’s cluster IP and port.

Let’s say an existing Service named my-db-service in the database namespace exposes a TCP port 5432.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: my-app-image:latest
        env:
        - name: DB_HOST

          value: {{ lookup "v1" "Service" "my-db-service" "database" | fromJson | get "spec.clusterIP" }}

        - name: DB_PORT

          value: {{ lookup "v1" "Service" "my-db-service" "database" | fromJson | get "spec.ports[0].port" }}

Notice how we’re accessing spec.ports[0].port. When you query a Service object, spec.ports is an array. We’re assuming the first port defined in the service is the one we want. You might need more sophisticated logic (e.g., iterating or filtering ports by name or targetPort) if your service has multiple ports.

The lookup function can also be used to check for the existence of resources and conditionally render parts of your template. For instance, you might want to create a Secret only if one doesn’t already exist with a specific name.


{{/* templates/my-secret.yaml */}}


{{- if not (lookup "v1" "Secret" .Release.Namespace "my-app-existing-secret") -}}

apiVersion: v1
kind: Secret
metadata:
  name: my-app-existing-secret

  namespace: {{ .Release.Namespace }}

type: Opaque
data:

  password: {{ "my-default-password" | b64enc }}


{{- end -}}

In this example, my-secret.yaml will only be rendered if a Secret named my-app-existing-secret does not already exist in the release’s namespace. If it exists, lookup will return its JSON representation, the not operator will evaluate to false, and the Secret definition will be skipped. This is useful for providing default configurations while allowing users to override them by pre-creating resources.

One critical aspect of lookup is its behavior during helm install vs. helm upgrade. During helm install, lookup will fail if the resource does not exist. However, during helm upgrade, lookup will not fail if the resource doesn’t exist; it will simply return nil. This difference is important for managing the lifecycle of your resources and avoiding unexpected errors during initial deployments.

The lookup function is a powerful tool for building more flexible and robust Helm charts that can adapt to existing infrastructure. It allows you to integrate with resources managed outside of Helm and to create charts that are less opinionated about the exact state of your Kubernetes cluster.

The next logical step after mastering lookup is understanding how to use include and template to modularize your Helm chart logic, especially when dealing with complex dependencies or conditional resource creation based on lookups.

Want structured learning?

Take the full Helm course →