env.dev

Kubernetes Env Variables: ConfigMaps, Secrets & Pod Spec

How to configure environment variables in Kubernetes: inline env, ConfigMaps, Secrets, the downward API, and best practices for managing configuration at scale.

Last updated:

Kubernetes offers several mechanisms for injecting environment variables into containers: inline env fields, envFrom bulk imports from ConfigMaps and Secrets, and the downward API for exposing pod metadata and resource limits. Choosing the right approach depends on whether the values are sensitive, how many variables you need, and whether they change between environments. This guide covers every method, explains ordering and dependency rules, and highlights the mistakes that cause the most debugging time in production clusters. If you are coming from Docker, see the Docker env variables guide for the natural progression into Kubernetes.

How do you set inline environment variables in a Pod spec?

The simplest approach is the env field directly in a container spec. Each entry is a name-value pair available inside the container at runtime.

yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
    - name: app
      image: my-app:1.0
      env:
        - name: NODE_ENV
          value: "production"
        - name: PORT
          value: "3000"
        - name: LOG_LEVEL
          value: "info"

Values must always be strings. Even numeric values like 3000 need quoting in YAML. This method works for a handful of variables but becomes unwieldy when you have dozens of entries shared across multiple Deployments.

How do you load environment variables from a ConfigMap?

A ConfigMap stores non-sensitive key-value pairs. Use envFrom with a configMapRef to inject all keys at once.

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  NODE_ENV: "production"
  LOG_LEVEL: "info"
  MAX_RETRIES: "5"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
        - name: app
          image: my-app:1.0
          envFrom:
            - configMapRef:
                name: app-config

Every key in the ConfigMap becomes an environment variable. You can add an optional prefix field to namespace the variables, for example prefix: APP_ turns LOG_LEVEL into APP_LOG_LEVEL.

How do you inject secrets as environment variables?

Secrets work like ConfigMaps but store data as base64-encoded values. Use envFrom with a secretRef to load them all, or valueFrom.secretKeyRef for individual keys.

yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  DATABASE_URL: cG9zdGdyZXM6Ly91c2VyOnBhc3NAZGI6NTQzMi9teWFwcA==
  API_KEY: c2stbGl2ZS1hYmMxMjM=
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
        - name: app
          image: my-app:1.0
          envFrom:
            - secretRef:
                name: app-secrets

To select a single key from a Secret instead of importing everything:

yaml
env:
  - name: DATABASE_URL
    valueFrom:
      secretKeyRef:
        name: app-secrets
        key: DATABASE_URL

Kubernetes decodes the base64 values automatically before injecting them into the container. Your application receives the plaintext value.

How do you expose pod metadata using the downward API?

The fieldRef mechanism exposes pod-level metadata as environment variables. This is called the downward API because it passes cluster information down into the container.

yaml
env:
  - name: POD_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.name
  - name: POD_NAMESPACE
    valueFrom:
      fieldRef:
        fieldPath: metadata.namespace
  - name: POD_IP
    valueFrom:
      fieldRef:
        fieldPath: status.podIP
  - name: NODE_NAME
    valueFrom:
      fieldRef:
        fieldPath: spec.nodeName
  - name: SERVICE_ACCOUNT
    valueFrom:
      fieldRef:
        fieldPath: spec.serviceAccountName

These values are useful for logging, tracing, and service discovery. The pod name and namespace are especially common in observability stacks where you want to correlate logs with specific pod instances.

How do you expose CPU and memory limits to a container?

The resourceFieldRef field exposes resource requests and limits. This is critical for runtimes that need to know their memory ceiling, such as the JVM or Node.js.

yaml
containers:
  - name: app
    image: my-app:1.0
    resources:
      requests:
        memory: "256Mi"
        cpu: "250m"
      limits:
        memory: "512Mi"
        cpu: "500m"
    env:
      - name: MEMORY_LIMIT
        valueFrom:
          resourceFieldRef:
            containerName: app
            resource: limits.memory
            divisor: "1Mi"
      - name: CPU_LIMIT
        valueFrom:
          resourceFieldRef:
            containerName: app
            resource: limits.cpu
            divisor: "1m"

The divisor field controls the unit of the output value. Without it, memory is returned in bytes and CPU in cores. Setting divisor: "1Mi" returns megabytes, and divisor: "1m" returns millicores.

How does variable ordering and dependency work?

Kubernetes evaluates env entries in the order they appear. A variable can reference a previously defined variable using $(VAR_NAME) syntax.

yaml
env:
  - name: DB_HOST
    value: "postgres.default.svc.cluster.local"
  - name: DB_PORT
    value: "5432"
  - name: DB_NAME
    value: "myapp"
  - name: DATABASE_URL
    value: "postgres://$(DB_HOST):$(DB_PORT)/$(DB_NAME)"

The DATABASE_URL resolves to postgres://postgres.default.svc.cluster.local:5432/myapp. If you reference a variable that has not been defined yet (or is loaded from a ConfigMap via envFrom), the reference is left as a literal string. Variables from envFrom are loaded before inline env entries, so inline entries can override envFrom values but cannot reference them using the $() syntax.

When should you use Secrets vs ConfigMaps?

Use each for its intended purpose:

Use ConfigMaps forUse Secrets for
Feature flags and togglesDatabase credentials
Application configuration (log levels, timeouts)API keys and tokens
Service URLs and endpointsTLS certificates and private keys
Configuration files (nginx.conf, app.properties)OAuth client secrets

Secrets are base64-encoded by default, which is not encryption. Anyone with RBAC access to read Secrets in a namespace can decode them. For real encryption at rest, enable EncryptionConfiguration on the API server or use an external secrets operator.

What are the best practices for managing secrets in Kubernetes?

Native Kubernetes Secrets have limited security guarantees. Production clusters should use an external secrets management solution. For team workflows around distributing the underlying credentials, see sharing env files securely.

  • External Secrets Operator (ESO) syncs secrets from AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, or HashiCorp Vault into Kubernetes Secrets automatically.
  • Sealed Secrets by Bitnami encrypts Secrets client-side so you can safely commit them to Git. Only the cluster controller can decrypt them.
  • SOPS with age or KMS encrypts secret YAML files at rest in your repository. Decrypt during CI/CD before applying — see the GitHub Actions CI/CD guide for runner-side decryption patterns.
  • RBAC restrictions — limit who can get or list Secrets at the namespace level. Avoid granting broad cluster-admin access.
  • Enable encryption at rest in etcd via EncryptionConfiguration. Without this, Secrets are stored as plaintext base64 in etcd.
yaml
# ExternalSecret (External Secrets Operator)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: app-secrets
  data:
    - secretKey: DATABASE_URL
      remoteRef:
        key: prod/my-app/database-url
    - secretKey: API_KEY
      remoteRef:
        key: prod/my-app/api-key

What are the most common mistakes?

Incorrect base64 encoding in Secrets:
yaml
# WRONG — double-encoded because the value already had a newline
data:
  PASSWORD: cGFzc3dvcmQK  # echo "password" | base64 (includes trailing newline)

# RIGHT — use echo -n to avoid the trailing newline
data:
  PASSWORD: cGFzc3dvcmQ=  # echo -n "password" | base64

# OR use stringData to let Kubernetes handle encoding
stringData:
  PASSWORD: "password"
Pods not picking up ConfigMap or Secret changes:

Updating a ConfigMap or Secret does not automatically restart pods that reference it via envFrom or valueFrom. Environment variables are set at container start and never refreshed. You must restart the pods manually or use a tool like stakater/Reloader to trigger rolling restarts when referenced resources change.

yaml
# Trigger a rollout restart after ConfigMap changes
# kubectl rollout restart deployment/my-app

# Or use Reloader annotations
metadata:
  annotations:
    reloader.stakater.com/auto: "true"
Forgetting that values must be strings:

YAML interprets unquoted true, false, and numbers as their native types. Always quote environment variable values in the value field to avoid type errors.

Using envFrom without checking for key conflicts:

When you load multiple ConfigMaps or Secrets via envFrom, duplicate keys silently overwrite each other. The last envFrom entry wins, and inline env entries override everything.

References

Was this helpful?

Read next

Env Variables Security: Secrets, Leaks & Best Practices

Why environment variables are not truly secure and what to do about it: secret rotation, leak detection, client-side risk, and secrets managers.

Continue →

Frequently Asked Questions

What is the difference between ConfigMaps and Secrets in Kubernetes?

ConfigMaps store non-sensitive configuration data as plain text key-value pairs. Secrets store sensitive data (passwords, tokens, keys) as base64-encoded values with additional access controls. Use ConfigMaps for feature flags and URLs, Secrets for credentials.

Do I need to restart pods after changing a ConfigMap?

Yes, if the ConfigMap is injected as environment variables. Kubernetes does not automatically restart pods when ConfigMaps change. Use kubectl rollout restart deployment/name or implement a config reloader like Reloader or stakater/Reloader.

How do I pass pod metadata as environment variables?

Use the downward API with valueFrom.fieldRef. You can expose the pod name (metadata.name), namespace (metadata.namespace), node name (spec.nodeName), pod IP (status.podIP), and labels/annotations.

Can I reference a ConfigMap key in a dependent variable?

No. The $(VAR_NAME) syntax only works with variables defined earlier in the same env list. Variables loaded via envFrom cannot be referenced this way. To compose a URL from ConfigMap values, use an init container or entrypoint script.

Should I use stringData or data in Secrets?

Use stringData for convenience during development — it accepts plaintext and Kubernetes encodes it automatically. In production pipelines, the stored Secret always uses data with base64 values. Both are equivalent at runtime.

What happens if a referenced ConfigMap or Secret does not exist?

The pod will fail to start with a CreateContainerConfigError. To make a reference optional, set optional: true on the configMapRef or secretRef.

Stay up to date

Get notified about new guides, tools, and cheatsheets.