ConfigMaps & Secrets

Medium 20 min read

Configuration Management in Kubernetes

ConfigMaps and Secrets decouple configuration from container images

This separation lets you change application behavior without rebuilding images, and keeps sensitive data out of your codebase.

ConfigMaps

Store non-sensitive configuration data in key-value pairs. Use for environment variables, configuration files, and command arguments.

Secrets

Hold sensitive information such as passwords, OAuth tokens, SSH keys, and TLS certificates. Base64 encoded, with optional encryption at rest.

ConfigMap vs Secret Comparison

Feature ConfigMap Secret
Purpose Non-sensitive configuration Sensitive data
Data Format Plain text Base64 encoded
Encryption at Rest Optional Recommended
RBAC Support Yes Yes (more restrictive)
Size Limit 1MB 1MB
Update Behavior Can be updated Can be updated

Best Practice

Always use ConfigMaps for non-sensitive data and Secrets for sensitive data. Never store sensitive information like passwords or API keys in ConfigMaps, even if they are "internal" to your cluster.

Working with ConfigMaps

Creating ConfigMaps

from-literals.sh
# Create a ConfigMap from literal key-value pairs
kubectl create configmap app-config \
  --from-literal=database_url=postgres://localhost:5432/mydb \
  --from-literal=cache_size=100 \
  --from-literal=log_level=debug
from-file.sh
# Create from a properties file
kubectl create configmap app-config --from-file=app.properties

# Create from multiple files
kubectl create configmap app-config \
  --from-file=config/ \
  --from-file=app.properties
configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  # Simple key-value pairs
  database_url: "postgres://localhost:5432/mydb"
  cache_size: "100"
  log_level: "debug"

  # Multi-line file content
  app.properties: |
    server.port=8080
    server.host=0.0.0.0
    debug.enabled=true

  nginx.conf: |
    server {
      listen 80;
      server_name example.com;

      location / {
        proxy_pass http://backend:8080;
      }
    }

Using ConfigMaps in Pods

configmap-env.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:latest
    env:
    # Individual keys
    - name: DATABASE_URL
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: database_url
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: app-config
          key: log_level

    # All keys from ConfigMap
    envFrom:
    - configMapRef:
        name: app-config
        prefix: APP_  # Optional prefix for env vars
configmap-volume.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config
      readOnly: true

    # Mount specific keys to specific paths
    - name: nginx-config
      mountPath: /etc/nginx/nginx.conf
      subPath: nginx.conf

  volumes:
  - name: config-volume
    configMap:
      name: app-config
      # Optional: specify permissions
      defaultMode: 0644

  - name: nginx-config
    configMap:
      name: app-config
      items:
      - key: nginx.conf
        path: nginx.conf
        mode: 0644

ConfigMap Updates

When a ConfigMap is updated:

  • Environment variables do NOT update automatically
  • Volume-mounted files update within ~1 minute (kubelet sync period)
  • SubPath mounts do NOT update automatically
  • Pods must be restarted to pick up env var changes

Working with Secrets

Types of Secrets

Opaque

Default secret type for arbitrary user-defined data. Most common type.

TLS

For storing TLS certificates and private keys for HTTPS.

Docker Registry

For authenticating with private Docker registries to pull images.

Service Account

Automatically created for service accounts to authenticate with the API server.

Creating and Managing Secrets

create-secrets.sh
# Create secret with literal values
kubectl create secret generic db-secret \
  --from-literal=username=dbuser \
  --from-literal=password='S3cur3P@ssw0rd'

# Create from files
kubectl create secret generic ssh-key-secret \
  --from-file=ssh-privatekey=/path/to/.ssh/id_rsa \
  --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub

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

# Create Docker registry secret
kubectl create secret docker-registry regcred \
  --docker-server=myregistry.io \
  --docker-username=user \
  --docker-password=pass \
  --docker-email=user@example.com
secret-stringdata.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
# stringData allows plain text (converted to base64 automatically)
stringData:
  username: admin
  password: 'S3cur3P@ssw0rd'
  config.yaml: |
    apiUrl: https://api.example.com
    apiKey: abc123xyz789
    timeout: 30
secret-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: secret-pod
spec:
  containers:
  - name: app
    image: myapp:latest
    env:
    # As environment variables
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: password

    volumeMounts:
    # As files in a volume
    - name: secret-volume
      mountPath: "/etc/secrets"
      readOnly: true

  volumes:
  - name: secret-volume
    secret:
      secretName: mysecret
      defaultMode: 0400  # Read-only for owner

  # For pulling images from private registry
  imagePullSecrets:
  - name: regcred

Security Warning

Secrets are only base64 encoded, NOT encrypted by default. Always enable encryption at rest in etcd, use RBAC to limit Secret access, consider external secret management systems, and never commit Secrets to version control.

Usage Patterns and Advanced Configuration

Application Configuration Pattern

A common production pattern combines ConfigMaps for non-sensitive settings with Secrets for credentials, both mounted into a Deployment.

app-config-pattern.yaml
# ConfigMap for app configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  application.yaml: |
    server:
      port: 8080
      host: 0.0.0.0
    features:
      cache: enabled
      rateLimit: 100
    logging:
      level: info
      format: json
---
# Secret for sensitive data
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
stringData:
  database.yaml: |
    host: db.example.com
    port: 5432
    username: appuser
    password: 'V3ryS3cur3P@ss'
---
# Deployment using both
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: app
        image: myapp:latest
        volumeMounts:
        - name: config
          mountPath: /app/config
        - name: secrets
          mountPath: /app/secrets
      volumes:
      - name: config
        configMap:
          name: app-config
      - name: secrets
        secret:
          secretName: app-secrets
          defaultMode: 0400

Environment-Specific Configuration

Use multiple ConfigMaps to layer base settings with environment-specific overrides.

env-config.yaml
# Base ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-base
data:
  APP_NAME: "MyApplication"
  LOG_FORMAT: "json"
---
# Production environment
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-prod
data:
  ENVIRONMENT: "production"
  API_URL: "https://api.example.com"
  DEBUG: "false"
  LOG_LEVEL: "warn"
---
# Pod that combines configurations
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: myapp:latest
    envFrom:
    # Load base config first
    - configMapRef:
        name: app-config-base
    # Then environment-specific (can override base)
    - configMapRef:
        name: app-config-prod

Dynamic Configuration Reload

config-reloader.yaml
# Deployment with config reloader sidecar
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-with-reloader
spec:
  template:
    spec:
      containers:
      - name: app
        image: myapp:latest
        volumeMounts:
        - name: config
          mountPath: /etc/config

      # Sidecar to watch for config changes
      - name: config-reloader
        image: jimmidyson/configmap-reload:v0.5.0
        args:
        - --volume-dir=/etc/config
        - --webhook-url=http://localhost:8080/reload
        volumeMounts:
        - name: config
          mountPath: /etc/config

      volumes:
      - name: config
        configMap:
          name: app-config

Immutable ConfigMaps and Secrets

immutable.yaml
# Immutable ConfigMap (Kubernetes 1.21+)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v1
immutable: true  # Cannot be updated once created
data:
  config.yaml: |
    version: v1.0.0
    features:
      newFeature: enabled
---
# Immutable Secret
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets-v1
immutable: true
type: Opaque
data:
  apiKey: YXBpLWtleS12YWx1ZQ==

Configuration Best Practices

  • Version your configs: Use versioned names (app-config-v1, app-config-v2)
  • Use namespaces: Organize configs by namespace for isolation
  • Validate configs: Test configuration changes in staging first
  • Keep configs small: Split large configs into multiple ConfigMaps
  • Use tools: Consider Helm or Kustomize for config management

Security Best Practices

Encryption at Rest

Enable etcd encryption for Secrets to protect data stored on disk.

RBAC Policies

Limit Secret access with fine-grained RBAC roles and bindings.

External Secrets

Use HashiCorp Vault or AWS Secrets Manager for production workloads.

Rotation

Regularly rotate sensitive credentials to limit exposure windows.

RBAC for Secrets

rbac-secrets.yaml
# Role to read specific secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
  namespace: production
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["app-secrets", "db-secrets"]
  verbs: ["get", "list"]
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-secrets
  namespace: production
subjects:
- kind: ServiceAccount
  name: app-service-account
  namespace: production
roleRef:
  kind: Role
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

Sealed Secrets Pattern

sealed-secrets.sh
# Install sealed-secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/controller.yaml

# Create a secret
echo -n mypassword | kubectl create secret generic mysecret \
  --dry-run=client \
  --from-file=password=/dev/stdin \
  -o yaml > mysecret.yaml

# Seal the secret (safe to commit to Git)
kubeseal -f mysecret.yaml -w mysealedsecret.yaml

External Secrets Operator

external-secrets.yaml
# SecretStore for AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secret-store
spec:
  provider:
    aws:
      service: SecretsManager
      region: us-west-2
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: aws-credentials
            key: access-key
          secretAccessKeySecretRef:
            name: aws-credentials
            key: secret-key
---
# ExternalSecret that syncs from AWS
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
spec:
  refreshInterval: 15m
  secretStoreRef:
    name: aws-secret-store
    kind: SecretStore
  target:
    name: app-secrets
    creationPolicy: Owner
  data:
  - secretKey: password
    remoteRef:
      key: prod/database/password

Security Anti-Patterns to Avoid

  • Storing secrets in ConfigMaps instead of Secrets
  • Hardcoding secrets in container images
  • Using secrets in Pod commands/args (visible in process listings)
  • Logging secret values in application output
  • Committing secrets to version control
  • Using default service account tokens unnecessarily

Practice Problems

Medium Multi-Environment Application

Deploy an application with different configurations for dev and prod environments. Create base configuration shared across environments, environment-specific overrides, and store database credentials securely.

Use multiple ConfigMaps with envFrom to layer configurations. The later ConfigMap in the list can override keys from the earlier one. Use a Secret for database credentials with secretKeyRef.

# base-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-base-config
data:
  APP_NAME: "MyApp"
  CACHE_TTL: "3600"
  MAX_CONNECTIONS: "100"
---
# prod-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-prod-config
data:
  ENVIRONMENT: "production"
  API_URL: "https://api.example.com"
  DEBUG: "false"
  LOG_LEVEL: "info"
---
# secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:
  username: dbadmin
  password: "Sup3rS3cur3!"
  connection-string: "postgresql://dbadmin:Sup3rS3cur3!@db:5432/myapp"
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: app
        image: myapp:latest
        envFrom:
        - configMapRef:
            name: app-base-config
        - configMapRef:
            name: app-prod-config
        env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password

Medium TLS Certificate Management

Configure HTTPS for your application using TLS certificates stored as Secrets. Generate certificates, create a TLS Secret, configure an Ingress, and mount certs in a pod.

Use kubectl create secret tls with --cert and --key flags. Reference the secret in an Ingress spec.tls block. For pod-level TLS, mount the secret as a volume with restrictive file permissions.

# Generate self-signed certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key -out tls.crt \
  -subj "/CN=myapp.example.com/O=myapp"

# Create TLS secret
kubectl create secret tls myapp-tls \
  --cert=tls.crt \
  --key=tls.key
# ingress-tls.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
spec:
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-tls
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-service
            port:
              number: 80

Hard Implement Secret Rotation

Design a system for rotating database credentials without downtime. Create versioned secrets, implement graceful credential rotation, use init containers for validation, and automate the process.

Use versioned secret names (db-secret-v1, db-secret-v2). Update the Deployment to reference the new secret version, which triggers a rolling update. Use an init container to validate the new credentials before the main container starts.

# Create versioned secrets
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials-v2
  labels:
    app: myapp
    version: "v2"
type: Opaque
stringData:
  username: dbadmin
  password: "N3wR0tat3dP@ss!"
---
# Deployment with init container for validation
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  strategy:
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  template:
    spec:
      initContainers:
      - name: validate-creds
        image: postgres:alpine
        command: ["sh", "-c"]
        args:
        - "pg_isready -h $DB_HOST -U $DB_USER || exit 1"
        env:
        - name: DB_USER
          valueFrom:
            secretKeyRef:
              name: db-credentials-v2
              key: username
      containers:
      - name: app
        image: myapp:latest
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials-v2
              key: password

Quick Reference

Common kubectl Commands

Command Description
kubectl create configmap NAME --from-literal=KEY=VAL Create ConfigMap from literal values
kubectl create configmap NAME --from-file=FILE Create ConfigMap from file
kubectl create secret generic NAME --from-literal=KEY=VAL Create Secret from literal values
kubectl create secret tls NAME --cert=CRT --key=KEY Create TLS Secret
kubectl get configmaps List all ConfigMaps
kubectl describe secret NAME View Secret metadata (not data)
kubectl get secret NAME -o jsonpath='{.data.KEY}' | base64 -d Decode a Secret value

Remember

ConfigMaps and Secrets are namespaced resources. A Pod can only reference ConfigMaps and Secrets in the same namespace. For cross-namespace access, replicate the resources or use external secret management tools.