ConfigMaps and Secrets

intermediate kubernetes configmaps secrets configuration

Hardcoding config values inside a container image is a bad idea. Every time a database URL or feature flag changes, we’d need to rebuild the image. Kubernetes solves this with ConfigMaps (for non-sensitive config) and Secrets (for sensitive stuff like passwords and API keys).

ConfigMaps

A ConfigMap holds key-value pairs of configuration data. We can create them in a few ways.

# From literal values
kubectl create configmap app-config \
  --from-literal=APP_ENV=production \
  --from-literal=LOG_LEVEL=info

# From a file
kubectl create configmap nginx-config --from-file=nginx.conf

# From an env file
kubectl create configmap app-config --from-env-file=.env

Or define it in YAML:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_ENV: "production"
  LOG_LEVEL: "info"
  DATABASE_HOST: "db-service"

Using ConfigMaps in Pods

We have two options: environment variables or mounted files.

As environment variables:

spec:
  containers:
    - name: app
      image: my-app:latest
      envFrom:
        - configMapRef:
            name: app-config        # injects ALL keys as env vars
      env:
        - name: SPECIFIC_VAR       # or pick specific keys
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: LOG_LEVEL

As mounted files:

spec:
  containers:
    - name: app
      image: my-app:latest
      volumeMounts:
        - name: config-volume
          mountPath: /etc/config    # each key becomes a file
  volumes:
    - name: config-volume
      configMap:
        name: app-config

With volume mounts, each key in the ConfigMap becomes a file in the mount path. So LOG_LEVEL becomes /etc/config/LOG_LEVEL with content info. The bonus? Volume-mounted ConfigMaps auto-update when the ConfigMap changes (with a small delay). Environment variables don’t — they need a Pod restart.

Secrets

Secrets work almost identically to ConfigMaps but are meant for sensitive data. The values are base64-encoded (not encrypted!).

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  DB_PASSWORD: cGFzc3dvcmQxMjM=    # base64 of "password123"
  DB_USER: YWRtaW4=                 # base64 of "admin"
# Easier: create from command line (handles encoding for us)
kubectl create secret generic db-credentials \
  --from-literal=DB_PASSWORD=password123 \
  --from-literal=DB_USER=admin

Using Secrets in Pods is the same pattern — secretRef instead of configMapRef:

envFrom:
  - secretRef:
      name: db-credentials

Important: Secrets Are NOT Encrypted by Default

This is a common interview gotcha. Kubernetes Secrets are only base64-encoded, which is not encryption. Anyone with kubectl get secret -o yaml access can decode them. To actually secure Secrets:

  • Enable encryption at rest in etcd (EncryptionConfiguration)
  • Use external secret managers like AWS Secrets Manager, HashiCorp Vault, or Sealed Secrets
  • Limit access with RBAC (don’t let every developer read Secrets)

When to Use Which

  • ConfigMap — non-sensitive config like log levels, feature flags, endpoint URLs, config files
  • Secret — passwords, tokens, TLS certificates, API keys

Immutable ConfigMaps and Secrets

Since Kubernetes 1.21, we can mark a ConfigMap or Secret as immutable: true. Once set, it can’t be changed — only deleted and recreated. This is useful because:

  • It protects against accidental updates
  • It improves cluster performance (the API server skips watching immutable objects)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v2
data:
  APP_ENV: "production"
immutable: true

In simple language, ConfigMaps and Secrets let us keep config outside our container images. ConfigMaps for regular config, Secrets for sensitive data. Just remember that Secrets need extra steps to be truly secure.