Kubernetes ConfigMaps and Secrets: Configuration Management

Hardcoding configuration into container images creates brittle, environment-specific artifacts that violate the twelve-factor app methodology. Every configuration change requires rebuilding images,...

Key Insights

  • ConfigMaps and Secrets decouple configuration from container images, enabling the same image to run across multiple environments without rebuilding
  • Native Kubernetes Secrets provide only base64 encoding, not encryption—use encryption at rest, RBAC, and consider external secret managers for production workloads
  • Immutable ConfigMaps and Secrets prevent accidental updates and enable safer rollouts by forcing explicit version changes through pod recreation

Introduction to Configuration Management in Kubernetes

Hardcoding configuration into container images creates brittle, environment-specific artifacts that violate the twelve-factor app methodology. Every configuration change requires rebuilding images, pushing to registries, and redeploying—a slow, error-prone process that defeats the agility Kubernetes promises.

Kubernetes provides two native primitives for externalizing configuration: ConfigMaps for non-sensitive data and Secrets for credentials and sensitive information. Both allow you to inject configuration at runtime, maintaining a single container image across development, staging, and production environments.

Here’s the anti-pattern you should avoid:

apiVersion: v1
kind: Pod
metadata:
  name: hardcoded-config
spec:
  containers:
  - name: app
    image: myapp:1.0
    env:
    - name: DATABASE_URL
      value: "postgres://prod-db:5432/myapp"  # Hardcoded!
    - name: API_KEY
      value: "sk-1234567890abcdef"  # Exposed in version control!

The proper approach separates configuration from code:

apiVersion: v1
kind: Pod
metadata:
  name: externalized-config
spec:
  containers:
  - name: app
    image: myapp:1.0
    envFrom:
    - configMapRef:
        name: app-config
    - secretRef:
        name: app-secrets

This separation enables configuration changes without image rebuilds and keeps sensitive data out of your codebase.

ConfigMaps: Managing Non-Sensitive Configuration

ConfigMaps store configuration data as key-value pairs, suitable for application settings, feature flags, configuration files, and any non-confidential information your applications need.

You can create ConfigMaps imperatively using kubectl:

# From literal values
kubectl create configmap app-config \
  --from-literal=log_level=debug \
  --from-literal=max_connections=100

# From a properties file
kubectl create configmap app-config \
  --from-file=application.properties

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

# From an entire directory
kubectl create configmap nginx-config \
  --from-file=conf.d/

For production, define ConfigMaps declaratively in YAML:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  log_level: "info"
  max_connections: "200"
  feature_flags.json: |
    {
      "enable_new_ui": true,
      "enable_beta_features": false
    }    
  application.properties: |
    server.port=8080
    server.compression.enabled=true    

Consume ConfigMaps as environment variables:

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: myapp:1.0
    env:
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: log_level
    - name: MAX_CONNECTIONS
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: max_connections

For configuration files, mount ConfigMaps as volumes:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.21
    volumeMounts:
    - name: config
      mountPath: /etc/nginx/conf.d
      readOnly: true
  volumes:
  - name: config
    configMap:
      name: nginx-config

This approach allows you to update configuration files without rebuilding container images. However, note that environment variables from ConfigMaps only update when pods restart, while mounted volumes can reflect changes after a propagation delay (typically under a minute).

Secrets: Handling Sensitive Data

Secrets store sensitive information like passwords, OAuth tokens, SSH keys, and TLS certificates. While structurally similar to ConfigMaps, Secrets receive special handling: they’re distributed only to nodes running pods that need them, stored in tmpfs (not written to disk), and can be encrypted at rest.

Kubernetes supports several Secret types:

# Generic/Opaque secret (most common)
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=supersecret

# TLS secret for HTTPS certificates
kubectl create secret tls tls-cert \
  --cert=path/to/tls.crt \
  --key=path/to/tls.key

# Docker registry credentials
kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=user \
  --docker-password=pass \
  --docker-email=user@example.com

In YAML, Secret values must be base64-encoded:

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  username: YWRtaW4=  # base64 encoded "admin"
  password: c3VwZXJzZWNyZXQ=  # base64 encoded "supersecret"

Use Secrets in pod specifications:

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: myapp:1.0
    env:
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: password

For file-based secrets like certificates, mount as volumes with restricted permissions:

apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: myapp:1.0
    volumeMounts:
    - name: tls-certs
      mountPath: /etc/tls
      readOnly: true
  volumes:
  - name: tls-certs
    secret:
      secretName: tls-cert
      defaultMode: 0400  # Read-only for owner

For private container registries, reference docker-registry secrets:

apiVersion: v1
kind: Pod
metadata:
  name: private-app
spec:
  imagePullSecrets:
  - name: regcred
  containers:
  - name: app
    image: registry.example.com/private/myapp:1.0

Best Practices and Patterns

Immutable ConfigMaps and Secrets prevent accidental modifications and enable safer rollouts:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v2
immutable: true
data:
  log_level: "info"

Once created, immutable ConfigMaps cannot be modified. Configuration changes require creating a new ConfigMap with a different name and updating pod specifications, forcing explicit version changes.

Version your configurations using naming conventions:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-20240115
  labels:
    app: myapp
    version: "20240115"
data:
  log_level: "info"

Use Kustomize for environment-specific overlays:

# base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  log_level: "info"

# overlays/production/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  log_level: "warn"
  max_connections: "500"

Apply with: kubectl apply -k overlays/production

Namespace your resources to isolate configurations between teams and environments. Never share Secrets across namespaces—create duplicates if necessary.

Security Considerations and Alternatives

Native Kubernetes Secrets have limitations. Base64 encoding is not encryption—anyone with API access can decode Secret values. Enable encryption at rest in your cluster configuration:

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-32-byte-key>
      - identity: {}

Restrict Secret access with RBAC:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
  namespace: production
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["app-secrets"]  # Specific secret only
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-app-secrets
  namespace: production
subjects:
- kind: ServiceAccount
  name: app-sa
roleRef:
  kind: Role
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

For production workloads, consider external secret managers. The External Secrets Operator syncs secrets from external sources:

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.example.com"
      path: "secret"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "myapp"
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
  target:
    name: app-secrets
  data:
  - secretKey: db-password
    remoteRef:
      key: database/production
      property: password

This approach centralizes secret management, provides audit trails, and enables secret rotation without Kubernetes API access.

Troubleshooting and Common Pitfalls

Pods fail to start when referencing non-existent ConfigMaps or Secrets. Check pod events:

kubectl describe pod myapp-pod

# Look for events like:
# Warning  Failed  Error: configmap "app-config" not found

ConfigMaps and Secrets have a 1MB size limit. For larger configurations, split into multiple resources or use external storage.

Environment variables from ConfigMaps update only on pod restart. Mounted volumes update automatically but with propagation delays. Verify mounted content:

kubectl exec -it myapp-pod -- cat /etc/config/application.properties

Check logs for configuration-related errors:

kubectl logs myapp-pod | grep -i config

When debugging Secret issues, verify the Secret exists and contains expected keys:

kubectl get secret app-secrets -o jsonpath='{.data}'
kubectl get secret app-secrets -o jsonpath='{.data.password}' | base64 -d

Remember that deleting a ConfigMap or Secret while pods are using it doesn’t immediately affect running containers—they retain their mounted volumes and environment variables until restart. However, new pods will fail to start.

ConfigMaps and Secrets form the foundation of configuration management in Kubernetes. Master these primitives, implement proper security controls, and consider external secret managers for production environments. Your deployment pipeline and operational teams will thank you.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.