[10/24] M is for ConfigMaps and Secrets: Managing Configuration


This is Post #9 in the Kubernetes A-to-Z Series

Reading Order: Previous: Volumes | Next: Ingress

Series Progress: 10/24 complete | Difficulty: Intermediate | Time: 20 min | Part 3/6: Configuration

Welcome to the ninth post in our Kubernetes A-to-Z Series! Now that you understand persistent storage, let’s explore ConfigMaps and Secrets - the Kubernetes primitives for managing application configuration. These resources enable you to decouple configuration from container images, following the twelve-factor app methodology.

Why Separate Configuration from Code?

Hardcoding configuration in container images creates several problems:

Hardcoded Configuration (Bad):
┌─────────────────────────────────────┐
│  Container Image                    │
│  ┌─────────────────────────────┐    │
│  │  Application Code           │    │
│  │  DB_HOST=prod-db.internal   │    │  Must rebuild for
│  │  API_KEY=abc123             │    │  each environment
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘

Externalized Configuration (Good):
┌─────────────────────────────────────┐
│  Container Image                    │
│  ┌─────────────────────────────┐    │
│  │  Application Code           │    │  Same image,
│  │  (reads from environment)   │    │  different config
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘


┌─────────────────────────────────────┐
│  ConfigMap / Secret                 │
│  DB_HOST, API_KEY, etc.             │
└─────────────────────────────────────┘

Benefits of External Configuration

  • Environment Portability: Same image across dev/staging/prod
  • Security: Secrets managed separately from code
  • Flexibility: Change config without rebuilding images
  • Auditability: Track configuration changes in Kubernetes

ConfigMaps

What is a ConfigMap?

A ConfigMap stores non-sensitive configuration data as key-value pairs. Applications can consume ConfigMaps as environment variables, command-line arguments, or configuration files.

Creating ConfigMaps

# From literal values
kubectl create configmap app-config \
  --from-literal=DB_HOST=postgres.default.svc \
  --from-literal=DB_PORT=5432 \
  --from-literal=LOG_LEVEL=info

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

# From directory
kubectl create configmap app-configs --from-file=./config/

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

ConfigMap YAML Definition

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
  namespace: production
data:
  # Simple key-value pairs
  DB_HOST: "postgres.production.svc"
  DB_PORT: "5432"
  DB_NAME: "webapp"
  LOG_LEVEL: "info"
  CACHE_TTL: "3600"

  # Multi-line configuration file
  app.properties: |
    server.port=8080
    server.host=0.0.0.0
    logging.level=INFO
    cache.enabled=true
    cache.ttl=3600

  # JSON configuration
  features.json: |
    {
      "featureA": true,
      "featureB": false,
      "maxRetries": 3
    }

Using ConfigMaps as Environment Variables

# pod-with-configmap-env.yaml
apiVersion: v1
kind: Pod
metadata:
  name: webapp
spec:
  containers:
  - name: webapp
    image: myapp:v1.0
    # Individual environment variables
    env:
    - name: DATABASE_HOST
      valueFrom:
        configMapKeyRef:
          name: webapp-config
          key: DB_HOST
    - name: DATABASE_PORT
      valueFrom:
        configMapKeyRef:
          name: webapp-config
          key: DB_PORT
    # All keys as environment variables
    envFrom:
    - configMapRef:
        name: webapp-config

Using ConfigMaps as Volumes

# pod-with-configmap-volume.yaml
apiVersion: v1
kind: Pod
metadata:
  name: webapp
spec:
  containers:
  - name: webapp
    image: myapp:v1.0
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config
      readOnly: true
  volumes:
  - name: config-volume
    configMap:
      name: webapp-config
      # Optional: select specific keys
      items:
      - key: app.properties
        path: application.properties
      - key: features.json
        path: features.json

ConfigMap with Specific File Permissions

# configmap-with-permissions.yaml
apiVersion: v1
kind: Pod
metadata:
  name: webapp
spec:
  containers:
  - name: webapp
    image: myapp:v1.0
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config
  volumes:
  - name: config-volume
    configMap:
      name: webapp-config
      defaultMode: 0644
      items:
      - key: app.properties
        path: app.properties
        mode: 0400

Secrets

What is a Secret?

A Secret stores sensitive data like passwords, tokens, and keys. Secrets are base64-encoded and can be encrypted at rest in etcd.

Secret Types

TypeDescription
OpaqueGeneric secret (default)
kubernetes.io/service-account-tokenService account token
kubernetes.io/dockerconfigjsonDocker registry credentials
kubernetes.io/tlsTLS certificate and key
kubernetes.io/basic-authBasic authentication
kubernetes.io/ssh-authSSH credentials

Creating Secrets

# From literal values
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=secretpassword123

# From file
kubectl create secret generic tls-cert \
  --from-file=tls.crt=./cert.pem \
  --from-file=tls.key=./key.pem

# Docker registry secret
kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=user \
  --docker-password=password \
  [email protected]

# TLS secret
kubectl create secret tls webapp-tls \
  --cert=./tls.crt \
  --key=./tls.key

Secret YAML Definition

# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: production
type: Opaque
data:
  # Base64 encoded values
  username: YWRtaW4=           # echo -n 'admin' | base64
  password: c2VjcmV0cGFzczEyMw==  # echo -n 'secretpass123' | base64
---
# Using stringData (auto-encoded)
apiVersion: v1
kind: Secret
metadata:
  name: api-credentials
  namespace: production
type: Opaque
stringData:
  # Plain text (will be base64 encoded automatically)
  API_KEY: "my-secret-api-key"
  API_SECRET: "my-secret-api-secret"

Using Secrets as Environment Variables

# pod-with-secret-env.yaml
apiVersion: v1
kind: Pod
metadata:
  name: webapp
spec:
  containers:
  - name: webapp
    image: myapp:v1.0
    env:
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: password
    # All keys as environment variables
    envFrom:
    - secretRef:
        name: api-credentials

Using Secrets as Volumes

# pod-with-secret-volume.yaml
apiVersion: v1
kind: Pod
metadata:
  name: webapp
spec:
  containers:
  - name: webapp
    image: myapp:v1.0
    volumeMounts:
    - name: secret-volume
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: secret-volume
    secret:
      secretName: db-credentials
      defaultMode: 0400

TLS Secret for Ingress

# tls-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: webapp-tls
  namespace: production
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRUdJTi... # Base64 encoded certificate
  tls.key: LS0tLS1CRUdJTi... # Base64 encoded private key

Docker Registry Secret

# docker-registry-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: regcred
  namespace: production
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJ1c2VybmFtZSI6InVzZXIiLCJwYXNzd29yZCI6InBhc3N3b3JkIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIiwiYXV0aCI6ImRYTmxjanB3WVhOemQyOXlaQT09In19fQ==

Complete Application Example

# complete-config-example.yaml
# ConfigMap for non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
  namespace: production
data:
  DB_HOST: "postgres.production.svc"
  DB_PORT: "5432"
  DB_NAME: "webapp"
  REDIS_HOST: "redis.production.svc"
  LOG_LEVEL: "info"
---
# Secret for sensitive data
apiVersion: v1
kind: Secret
metadata:
  name: webapp-secrets
  namespace: production
type: Opaque
stringData:
  DB_PASSWORD: "super-secret-password"
  REDIS_PASSWORD: "redis-secret-password"
  JWT_SECRET: "jwt-signing-secret"
---
# Deployment using both
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: myapp:v1.0
        ports:
        - containerPort: 8080
        envFrom:
        - configMapRef:
            name: webapp-config
        - secretRef:
            name: webapp-secrets
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"

Environment-Specific Configuration

Using Kustomize Overlays

# base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
data:
  LOG_LEVEL: "info"
  CACHE_ENABLED: "true"

# overlays/development/configmap-patch.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
data:
  LOG_LEVEL: "debug"
  DB_HOST: "postgres.development.svc"

# overlays/production/configmap-patch.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
data:
  LOG_LEVEL: "warn"
  DB_HOST: "postgres.production.svc"

Immutable ConfigMaps and Secrets

For performance and safety:

# immutable-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config-v1
data:
  DB_HOST: "postgres.production.svc"
  LOG_LEVEL: "info"
immutable: true

Automatic Reload on Config Changes

Using Pod Annotations for Rolling Updates

# deployment-with-config-hash.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  template:
    metadata:
      annotations:
        # Change this to trigger rolling update
        configmap-hash: "abc123"
    spec:
      containers:
      - name: webapp
        image: myapp:v1.0
        envFrom:
        - configMapRef:
            name: webapp-config

Security Best Practices

1. Use RBAC to Restrict Secret Access

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

2. Enable Encryption at Rest

# encryption-config.yaml (on API server)
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
  - secrets
  providers:
  - aescbc:
      keys:
      - name: key1
        secret: <base64-encoded-secret>
  - identity: {}

3. Use External Secret Management

# external-secrets-example.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: webapp-secrets
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmanager
    kind: SecretStore
  target:
    name: webapp-secrets
  data:
  - secretKey: DB_PASSWORD
    remoteRef:
      key: production/webapp/db-password

Troubleshooting

# Check ConfigMap
kubectl get configmap webapp-config -o yaml
kubectl describe configmap webapp-config

# Check Secret (values are hidden)
kubectl get secret webapp-secrets -o yaml
kubectl describe secret webapp-secrets

# Decode secret value
kubectl get secret webapp-secrets -o jsonpath='{.data.DB_PASSWORD}' | base64 -d

# Check if pod has correct env vars
kubectl exec -it webapp-xxx -- env | grep DB_

# Check mounted config files
kubectl exec -it webapp-xxx -- cat /etc/config/app.properties

# Check events
kubectl get events --field-selector involvedObject.name=webapp-config

Commands Reference

# ConfigMap Management
kubectl create configmap app-config --from-literal=KEY=value
kubectl create configmap app-config --from-file=config.properties
kubectl get configmaps
kubectl describe configmap app-config
kubectl delete configmap app-config

# Secret Management
kubectl create secret generic db-creds --from-literal=password=secret
kubectl create secret tls webapp-tls --cert=tls.crt --key=tls.key
kubectl get secrets
kubectl describe secret db-creds
kubectl delete secret db-creds

# Decode Secret
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d

Key Takeaways

  • ConfigMaps store non-sensitive configuration data
  • Secrets store sensitive data (base64 encoded, can be encrypted at rest)
  • Both can be consumed as environment variables or volume mounts
  • Use immutable ConfigMaps/Secrets for performance
  • Implement RBAC to restrict secret access
  • Consider external secret managers for production

Next Steps

Now that you understand ConfigMaps and Secrets, you’re ready to explore Ingress in the next post. We’ll learn how to manage external access to services, configure routing rules, and implement TLS termination.

Resources for Further Learning


Series Navigation:

Complete Series: Kubernetes A-to-Z Series Overview