Beyond ConfigMaps and Secrets, Kubernetes provides ways to inject Pod-specific runtime information into containers — the Pod's name, namespace, IP, node, resource limits, and labels. This is the Downward API: a way for Pods to learn about themselves without calling the API server.

1. All Environment Variable Sources

A container's env vars can come from five sources:

SourceFieldUse Case
Static valueenv.valueHardcoded config, simple defaults
ConfigMapenv.valueFrom.configMapKeyRefNon-sensitive config
Secretenv.valueFrom.secretKeyRefPasswords, tokens
Downward API (field)env.valueFrom.fieldRefPod metadata (name, namespace, IP, node)
Downward API (resource)env.valueFrom.resourceFieldRefContainer resource limits/requests

Complete Example: All Sources in One Pod

spec:
  containers:
    - name: app
      image: myapp:latest
      env:
        # 1. Static value
        - name: APP_ENV
          value: production
        
        # 2. From ConfigMap
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: LOG_LEVEL
        
        # 3. From Secret
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-creds
              key: password
        
        # 4. Downward API — Pod field
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        
        # 5. Downward API — Resource field
        - name: MEMORY_LIMIT
          valueFrom:
            resourceFieldRef:
              containerName: app
              resource: limits.memory

Dependent Environment Variables

env:
  - name: DB_HOST
    value: postgres.default.svc
  - name: DB_PORT
    value: "5432"
  - name: DATABASE_URL
    value: "postgresql://$(DB_HOST):$(DB_PORT)/mydb"   # ← variable interpolation!
# K8s resolves $(VAR_NAME) from previously-defined env vars
# Order matters: referenced vars must be defined above
$(VAR_NAME) expansion: Kubernetes expands $(VAR) references using previously defined env vars (in order). This is done at Pod creation time, not shell expansion. Use $$(VAR) to escape if you need a literal $(VAR) in the value.

2. Downward API — fieldRef

Inject Pod metadata into the container without calling the API server:

Available Fields

fieldPathValue ExampleUse Case
metadata.nameweb-abc123Logging, tracing (identify Pod)
metadata.namespaceproductionMulti-tenant apps that need to know their namespace
metadata.uida1b2c3d4-...Unique correlation IDs
metadata.labels['key']v2Version-aware behavior
metadata.annotations['key']team-platformInjected metadata from CI/CD
spec.nodeNameworker-3Node-aware routing, logging
spec.serviceAccountNameweb-saIdentity-aware behavior
status.podIP10.244.1.5Self-registration with service discovery
status.hostIP192.168.1.10Node-level metrics endpoint
env:
  - name: MY_POD_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.name
  - name: MY_POD_IP
    valueFrom:
      fieldRef:
        fieldPath: status.podIP
  - name: MY_NODE
    valueFrom:
      fieldRef:
        fieldPath: spec.nodeName
  - name: MY_VERSION
    valueFrom:
      fieldRef:
        fieldPath: metadata.labels['version']

resourceFieldRef — Container Resources

env:
  - name: MEMORY_LIMIT
    valueFrom:
      resourceFieldRef:
        containerName: app          # which container (required in multi-container)
        resource: limits.memory     # in bytes by default
        divisor: "1Mi"              # convert to MiB (optional)
  - name: CPU_REQUEST
    valueFrom:
      resourceFieldRef:
        resource: requests.cpu
        divisor: "1m"               # in millicores
resourceReturns
requests.cpuCPU requests (default: cores as decimal)
limits.cpuCPU limits
requests.memoryMemory requests (default: bytes)
limits.memoryMemory limits
Common pattern: Java apps need -Xmx to match the container memory limit. Use resourceFieldRef to inject the limit, then set -Xmx to ~75% of it. This prevents OOMKill from the JVM allocating more than the cgroup allows.

3. Downward API via Volume

Labels and annotations can change after Pod creation (unlike most metadata). To see live updates, use a downwardAPI volume instead of env vars:

spec:
  containers:
    - name: app
      volumeMounts:
        - name: pod-info
          mountPath: /etc/podinfo
          readOnly: true
  volumes:
    - name: pod-info
      downwardAPI:
        items:
          - path: "labels"           # → /etc/podinfo/labels
            fieldRef:
              fieldPath: metadata.labels
          - path: "annotations"      # → /etc/podinfo/annotations
            fieldRef:
              fieldPath: metadata.annotations
          - path: "cpu_limit"        # → /etc/podinfo/cpu_limit
            resourceFieldRef:
              containerName: app
              resource: limits.cpu
              divisor: "1m"
# Inside the container:
cat /etc/podinfo/labels
# app="web"
# version="v2"
# environment="production"

cat /etc/podinfo/cpu_limit
# 500

Env Var vs Volume: When to Use Each

MethodUpdates Live?Best For
Env var (fieldRef)❌ No (fixed at start)Immutable info: Pod name, namespace, node, IP
Volume (downwardAPI)✅ Yes (labels/annotations change)Labels, annotations that may be updated post-creation
Labels can change, Pod name can't. Use env vars for things that are fixed for the Pod's lifetime (name, namespace, IP, node). Use volumes for things that can be updated dynamically (labels, annotations). The kubelet updates volume files when labels/annotations change.

4. Practical Production Uses

PatternImplementation
Structured loggingInject POD_NAME, NAMESPACE, NODE as env vars → include in every log line
JVM memory tuningInject limits.memory → set -Xmx to 75% of limit
Prometheus metricsInject POD_NAME → use as instance label in metrics
Service registrationInject status.podIP → register with Consul/etcd
Canary detectionMount labels as volume → sidecar reads version label to adjust behavior
Cost allocationInject metadata.labels['team'] → tag cloud metrics by team
Every production Pod should inject at least POD_NAME and NAMESPACE as env vars. Without them, correlating logs and metrics to a specific Pod instance is painful. Most logging frameworks (Fluentd, structured logging) use these to enrich log entries automatically.

Summary

ConceptKey Point
5 env var sourcesStatic, ConfigMap, Secret, fieldRef, resourceFieldRef
$(VAR) expansionReference earlier env vars — order matters
fieldRefPod metadata: name, namespace, IP, node, labels, annotations
resourceFieldRefContainer CPU/memory requests and limits
downwardAPI volumeFiles that auto-update when labels/annotations change
Env vars are fixedSet at container start, never change during lifetime

📝 Quiz: Environment Variables & the Downward API

Q1: How does a container learn its own Pod IP without calling the Kubernetes API?

Use the Downward API with fieldRef:
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP

The kubelet injects this at Pod start — no API server call needed.

Q2: You define DATABASE_URL: "postgres://$(DB_HOST):$(DB_PORT)/mydb". DB_PORT is defined AFTER DATABASE_URL in the env list. What value does DATABASE_URL get?

DATABASE_URL will be postgres://<DB_HOST_value>:$(DB_PORT)/mydb. $(DB_HOST) is expanded (defined earlier), but $(DB_PORT) is left as a literal string because it hasn't been defined yet. Order matters — referenced vars must be defined above.

Q3: A label on a running Pod is changed via kubectl label pod web version=v3. An env var uses fieldRef: metadata.labels['version']. Does the env var update?

No. Environment variables are fixed at container start and never change. To see live label updates, use a downwardAPI volume mount instead — the file will be updated when the label changes.

Q4: A Java app is OOMKilled. Its container has limits.memory: 512Mi. How do you set JVM heap correctly using the Downward API?

Inject the memory limit:
- name: MEM_LIMIT
valueFrom:
resourceFieldRef:
resource: limits.memory
divisor: "1Mi"

Then in the container command: -Xmx$(echo "$MEM_LIMIT * 75 / 100" | bc)m or use a modern JVM with -XX:MaxRAMPercentage=75.0 which reads cgroup limits directly.

Q5: What's the difference between fieldRef and resourceFieldRef?

fieldRef: Exposes Pod-level metadata — name, namespace, UID, IP, node name, labels, annotations. These are the same for all containers in the Pod.
resourceFieldRef: Exposes container-level resource settings — CPU/memory requests and limits. These can differ between containers in the same Pod (requires containerName in multi-container Pods).

Q6: You need to pass ALL of a Pod's labels to a container. Can you do this with env vars?

No — not as env vars. fieldRef for labels requires a specific key: metadata.labels['key']. To expose ALL labels (without knowing them in advance), use a downwardAPI volume with fieldRef: metadata.labels. This creates a file containing all labels as key=value pairs.