MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

Kubernetes Secrets and ConfigMaps: Managing Configuration

Manage K8s configuration: secrets, configmaps, sealed secrets, and external vaults.

KubernetesSecretsConfigMapsDevOps

By MinhVo

Introduction

Configuration management is one of the first challenges you encounter when deploying applications to Kubernetes. Every application needs configuration—database connection strings, API keys, feature flags, and environment-specific settings. Kubernetes provides two native primitives for this: ConfigMaps for non-sensitive configuration and Secrets for sensitive data. While these resources seem simple on the surface, using them correctly in production requires understanding their security implications, update mechanisms, and integration patterns.

Mismanagement of configuration is a leading cause of security breaches and production incidents. Hardcoded credentials in container images, unencrypted Secrets in etcd, and missing configuration validation have caused countless outages. The 2019 Capital One breach, which exposed over 100 million customer records, was partly attributed to misconfigured access controls on cloud resources. In the Kubernetes world, a leaked Secret committed to a public GitHub repository can be scraped by automated bots within seconds, giving attackers immediate access to your infrastructure. These real-world consequences make proper configuration management not just a best practice but a survival skill for modern engineering teams.

The landscape of Kubernetes configuration management has matured significantly since the early days of the project. What started as simple key-value stores has evolved into an ecosystem of tools and patterns designed to handle the complexity of enterprise deployments. Organizations running hundreds of microservices across multiple clusters and cloud providers need configuration strategies that scale with their infrastructure. This guide covers Kubernetes configuration management from basics to advanced patterns, including sealed secrets, external secret managers, and GitOps-compatible approaches. Whether you are running a single development cluster or managing a fleet of production environments across multiple regions, the patterns described here will help you build a robust and secure configuration management strategy.

Configuration management architecture

Understanding Kubernetes Secrets and ConfigMaps: Core Concepts

ConfigMaps store non-sensitive configuration data as key-value pairs. They can contain entire configuration files, command-line arguments, or environment variable values. ConfigMaps decouple configuration from container images, allowing the same image to run with different configurations across environments.

Secrets store sensitive data like passwords, tokens, and certificates. Kubernetes provides several Secret types: Opaque (generic), kubernetes.io/tls (TLS certificates), kubernetes.io/dockerconfigjson (registry credentials), and kubernetes.io/service-account-token (service account tokens). Each type has a defined structure that Kubernetes validates.

A critical misconception is that Kubernetes Secrets are encrypted by default. They are not. Secrets are base64-encoded (not encrypted) and stored in etcd in plain text. Anyone with access to etcd or the Kubernetes API can read Secret values by simply running kubectl get secret <name> -o jsonpath='{.data}' and base64-decoding the output. This is a fundamental security gap that many teams discover too late. In penetration testing engagements, security researchers frequently find that Kubernetes Secrets contain production database passwords, API keys for third-party services, and even private TLS certificates—all readable by anyone with cluster admin access. This is why additional security measures—encryption at rest, RBAC policies, and external secret managers—are essential for production deployments.

The distinction between ConfigMaps and Secrets is not just about sensitivity. ConfigMaps are designed for configuration data that might change between environments but isn't secret—think feature flags, logging levels, service endpoints, and UI configuration. Secrets are designed for credentials and cryptographic material that must be protected from unauthorized access. Mixing these concerns creates security risks: storing sensitive data in ConfigMaps bypasses any RBAC restrictions you've placed on Secrets, while storing non-sensitive data in Secrets adds unnecessary management overhead and can trigger false positives in security scanning tools.

Both ConfigMaps and Secrets can be consumed by pods in three ways: as environment variables, as files mounted in a volume, or as container command arguments. Each method has different implications for configuration updates, security, and application behavior. Environment variables are the simplest approach but have significant limitations: they cannot be updated without restarting the pod, they are visible in process listings and container inspection, and they have a combined size limit that varies by operating system (typically around 128KB on Linux). Volume-mounted files, by contrast, support automatic updates, can handle large configuration files, and are not visible in process environment dumps. For these reasons, volume mounts are generally preferred for production workloads, especially when dealing with sensitive data.

When you update a ConfigMap or Secret, the behavior depends on how it's consumed. Environment variables are set once at pod startup and don't change until the pod is restarted. Mounted volumes, however, are updated automatically by the kubelet—though there's a propagation delay of up to the kubelet's sync period (typically 60-90 seconds). This difference is crucial when designing configuration update strategies.

Architecture and Design Patterns

ConfigMap Structure and Consumption

ConfigMaps support two data formats: the data field for UTF-8 strings and the binaryData field for binary content encoded in base64. A single ConfigMap can contain multiple keys, each representing a different configuration value or file.

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  # Simple key-value pairs
  DATABASE_HOST: "postgres.default.svc.cluster.local"
  DATABASE_PORT: "5432"
  LOG_LEVEL: "info"
  # Entire configuration files
  nginx.conf: |
    server {
      listen 80;
      server_name _;
      location / {
        proxy_pass http://backend:8080;
        proxy_set_header Host $host;
      }
    }
  application.yml: |
    server:
      port: 8080
    database:
      host: ${DATABASE_HOST}
      port: ${DATABASE_PORT}

Secret Types and Their Structures

Different Secret types require specific data keys:

# Opaque Secret (generic)
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
stringData:  # Use stringData for plain text (auto-encoded to base64)
  username: admin
  password: "s3cur3-p@ssw0rd"
---
# TLS Secret
apiVersion: v1
kind: Secret
metadata:
  name: tls-secret
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded-cert>
  tls.key: <base64-encoded-key>
---
# Docker Registry Secret
apiVersion: v1
kind: Secret
metadata:
  name: registry-creds
type: kubernetes.io/dockerconfigjson
stringData:
  .dockerconfigjson: |
    {
      "auths": {
        "registry.example.com": {
          "username": "user",
          "password": "pass",
          "auth": "dXNlcjpwYXNz"
        }
      }
    }

Encryption at Rest

To encrypt Secrets in etcd, configure an EncryptionConfiguration. This is a critical security hardening step that many teams overlook during initial cluster setup, only realizing the gap during security audits or compliance reviews. Without encryption at rest, anyone who gains access to the etcd database files on disk—including backup operators, storage administrators, and cloud provider personnel—can read every Secret in the cluster. The encryption uses envelope encryption: a local data encryption key (DEK) encrypts each Secret, and the DEK itself is encrypted by a key encryption key (KEK) that you configure. This design allows key rotation without re-encrypting all data, because you only need to rotate the KEK.

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

Apply the configuration by adding --encryption-provider-config=/etc/kubernetes/enc/enc.yaml to the kube-apiserver manifest. After enabling, re-encrypt all existing secrets with kubectl get secrets --all-namespaces -o json | kubectl replace -f -.

Step-by-Step Implementation

Mounting ConfigMaps as Files

The most flexible consumption method mounts each ConfigMap key as a file in a volume. This approach has several advantages over environment variables: mounted files can be updated without restarting the pod, they support hierarchical configuration with directory structures, and they are not visible through container inspection tools that display process environments. When a ConfigMap is mounted as a volume, the kubelet creates a series of symlinks that point to a projected volume containing the ConfigMap data. The kubelet periodically checks for updates (controlled by the --sync-frequency flag, defaulting to one minute) and atomically replaces the files when changes are detected. This means your application can implement file-watching to pick up configuration changes within seconds of the ConfigMap being updated.

The key decision when mounting ConfigMaps is whether to mount the entire ConfigMap as a directory or to select individual keys. Mounting the entire ConfigMap is simpler and automatically picks up new keys when the ConfigMap is updated. Selecting individual keys with the items field gives you more control over which files are present and allows you to rename keys to filenames that your application expects.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: app
        image: nginx:1.25
        volumeMounts:
        - name: config-volume
          mountPath: /etc/nginx/conf.d
          readOnly: true
        - name: app-config
          mountPath: /app/config
          readOnly: true
      volumes:
      - name: config-volume
        configMap:
          name: nginx-config
      - name: app-config
        configMap:
          name: app-config
          items:
          - key: application.yml
            path: config.yml

Injecting Secrets as Environment Variables

For applications that read configuration from environment variables, injecting Secrets via env or envFrom is the simplest approach. However, this method has significant security implications that are often overlooked. Environment variables are visible to any process running in the container, included in debug logs and error reports, and can be extracted through container inspection APIs. They also cannot be updated without restarting the pod, which means credential rotation always requires a deployment. Despite these limitations, environment variable injection remains popular because it requires zero application code changes—most application frameworks already support reading configuration from environment variables out of the box.

When injecting multiple Secrets at once using envFrom, be aware that each key becomes a separate environment variable. If your Secret contains a key named password and your application happens to have a built-in environment variable with the same name, the Secret value will override it. To avoid these collisions, always use the prefix field with envFrom to namespace your injected variables.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-server
  template:
    spec:
      containers:
      - name: api
        image: my-api:1.0
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: url
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: api-keys
              key: primary
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: LOG_LEVEL
        # Use envFrom to inject all keys from a ConfigMap
        envFrom:
        - configMapRef:
            name: feature-flags
          prefix: FF_

Using SubPath for Individual Files

When you need to mount a single file without overwriting the entire directory:

containers:
- name: app
  image: my-app:1.0
  volumeMounts:
  - name: config-volume
    mountPath: /app/config/overrides.yml
    subPath: overrides.yml
    readOnly: true
volumes:
- name: config-volume
  configMap:
    name: app-config

Warning: SubPath mounts do not receive automatic ConfigMap updates. The kubelet only propagates updates to volume-mounted ConfigMaps, not SubPath mounts. Use this only when the application can't handle the entire directory being replaced.

Secret management workflow

Real-World Use Cases and Case Studies

Use Case 1: Multi-Environment Configuration with Overlays

Organizations running dev, staging, and production environments use ConfigMap overlays to manage environment-specific configuration. A base ConfigMap contains shared defaults, while environment-specific ConfigMaps override specific values. Applications merge these at startup, with environment-specific values taking precedence. This pattern is particularly powerful when combined with Kustomize, which provides built-in overlay support. In a typical setup, you define a base set of ConfigMaps in your base/ directory and create environment-specific patches in overlays/dev/, overlays/staging/, and overlays/production/ directories. Kustomize handles the merging automatically, ensuring that the production overlay can override database endpoints, increase connection pool sizes, and enable monitoring endpoints without touching the base configuration. The key benefit of this approach is that it eliminates configuration drift between environments while maintaining clear boundaries between what is shared and what is environment-specific.

# base-config ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-base
data:
  LOG_LEVEL: "info"
  CACHE_TTL: "300"
  MAX_CONNECTIONS: "100"
---
# production-config ConfigMap (overrides)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-production
data:
  LOG_LEVEL: "warn"
  MAX_CONNECTIONS: "500"
  ENABLE_METRICS: "true"

Use Case 2: Rotating Database Credentials

When using external secret managers like HashiCorp Vault, the Operator pattern handles credential rotation. A sidecar container watches for credential changes and updates the mounted Secret, triggering the application to reload its database connection pool without restarting. Credential rotation is a compliance requirement in many industries, with standards like PCI-DSS mandating rotation every 90 days and SOC 2 requiring rotation procedures as part of security controls. Manual rotation is fraught with risk: forgotten connections to old credentials cause cascading failures during rotation windows, and human error during off-hours rotation scripts has caused multiple high-profile outages.

The sidecar pattern solves this elegantly. The Vault Agent sidecar authenticates to Vault using the pod's Kubernetes service account, retrieves the latest credentials, and writes them to a shared volume. The application watches this volume for changes and re-establishes its database connection pool with the new credentials. This approach achieves zero-downtime rotation because the application always has access to valid credentials during the transition period. The Vault Agent handles lease renewal transparently, only writing new credentials when the Vault lease actually changes, which minimizes unnecessary connection pool churn.

containers:
- name: vault-agent
  image: hashicorp/vault:1.15
  args:
  - agent
  - -config=/vault/config/agent.hcl
  volumeMounts:
  - name: vault-config
    mountPath: /vault/config
  - name: secrets
    mountPath: /vault/secrets
- name: app
  image: my-api:1.0
  volumeMounts:
  - name: secrets
    mountPath: /app/secrets
    readOnly: true

Use Case 3: TLS Certificate Management

cert-manager stores issued certificates as TLS Secrets. Applications mount these Secrets to serve HTTPS traffic. When cert-manager renews a certificate, it updates the Secret, and the kubelet propagates the new files to the pod. Applications using file-watching libraries (like fsnotify) can hot-reload certificates without restart. The cert-manager project has become the de facto standard for certificate lifecycle management in Kubernetes, supporting automated issuance from Let's Encrypt, HashiCorp Vault, Venafi, and other certificate authorities. It handles the entire certificate lifecycle: requesting, issuance, renewal, and revocation. For organizations running internal PKI infrastructure, cert-manager can integrate with enterprise certificate authorities to issue certificates that chain to internal root CAs, enabling mutual TLS between services without the operational burden of manual certificate management.

Use Case 4: Feature Flag Management with ConfigMaps

Modern application teams use feature flags to decouple deployment from release. ConfigMaps provide a natural storage mechanism for feature flag state, allowing operations teams to toggle features without redeploying applications. Tools like OpenFeature define a standard API for feature flag evaluation, and ConfigMap-backed providers can read flag state from mounted volumes. This pattern is particularly effective in canary deployment scenarios where you want to enable a feature for a subset of pods before rolling it out to the entire fleet. By mounting different ConfigMaps to different pod groups, you can create feature flag experiments that are managed entirely through Kubernetes resource changes rather than application code modifications.

Best Practices for Production

  1. Enable encryption at rest: Configure EncryptionConfiguration on the API server to encrypt Secrets in etcd. Use a strong encryption key and rotate it periodically.

  2. Implement RBAC for Secrets: Restrict Secret access using Role and RoleBinding resources. Grant read access only to service accounts that need specific Secrets.

  3. Use external secret managers for production: Tools like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault provide audit logging, automatic rotation, and fine-grained access control that Kubernetes Secrets lack.

  4. Never commit Secrets to Git: Even with encryption, storing Secrets in version control creates audit trail risks. Use Sealed Secrets or External Secrets Operator for GitOps-compatible secret management.

  5. Use stringData for initial creation: The stringData field accepts plain text and automatically encodes to base64. Use it for initial creation, then switch to data for subsequent updates to avoid accidental exposure.

  6. Implement configuration validation: Use admission webhooks or OPA/Gatekeeper policies to validate ConfigMap and Secret contents before they're applied to the cluster.

  7. Use immutable ConfigMaps for static configuration: Set immutable: true on ConfigMaps that don't change. This prevents accidental modifications and improves performance by reducing API server watch load.

  8. Label and annotate configuration resources: Add labels like app.kubernetes.io/component: config and annotations like config-version: "2.1" to track configuration changes and enable selective updates.

Common Pitfalls and Solutions

Even experienced Kubernetes operators fall into configuration management traps. Understanding these pitfalls before they occur in production saves countless hours of debugging and incident response. The most common mistake teams make is treating Kubernetes configuration the same way they treated configuration in virtual machine environments, where applications read from local files that persist across restarts. In Kubernetes, pods are ephemeral and can be replaced at any time, which means configuration must be externalized and version-controlled.

PitfallImpactSolution
Using SubPath mountsNo automatic ConfigMap updatesMount entire volumes when possible; use file-watching in the application
Storing Secrets in Git reposCredential exposure in version historyUse Sealed Secrets or External Secrets Operator
Not enabling encryption at restSecrets readable in etcdConfigure EncryptionConfiguration on the API server
Assuming Secrets are encryptedFalse sense of securityTreat Secrets as base64-encoded; add encryption and RBAC
ConfigMap size limitsTruncation of large configsConfigMaps are limited to 1 MiB; split large configs across multiple ConfigMaps
Missing config validationRuntime errors from typosUse admission webhooks to validate config before applying
Hardcoded namespace referencesBreaks multi-namespace deploymentsUse downward API to inject namespace dynamically
No config versioningImpossible to roll back changesAppend version suffixes to ConfigMap names and use deployment annotations

Performance Optimization

Large ConfigMaps and Secrets can impact API server performance. When a ConfigMap is updated, the API server notifies all watchers, including kubelets on every node. For ConfigMaps referenced by many pods, updates generate significant API server traffic.

Use immutable ConfigMaps for configuration that doesn't change at runtime. This eliminates watch overhead and prevents accidental modifications:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v2
immutable: true
data:
  config.yml: |
    server:
      port: 8080

When configuration changes are required, create a new ConfigMap with a version suffix, update the Deployment to reference it, and perform a rolling update:

# Create new version
kubectl create configmap app-config-v3 --from-file=config.yml --dry-run=client -o yaml | \
  kubectl apply -f -
 
# Update deployment to use new config
kubectl set env deployment/app-app --from=configmap/app-config-v3

For applications that need hot-reloading without restarts, implement a configuration watcher that reads mounted files and applies changes dynamically.

import { watch } from 'fs';
import { readFileSync } from 'fs';
 
class ConfigWatcher {
  private config: Map<string, string> = new Map();
  private listeners: Array<(config: Map<string, string>) => void> = [];
 
  constructor(private configPath: string) {
    this.load();
    watch(configPath, { recursive: true }, () => this.load());
  }
 
  private load() {
    try {
      const content = readFileSync(`${this.configPath}/config.yml`, 'utf-8');
      // Parse and update config
      this.config.set('raw', content);
      this.listeners.forEach(fn => fn(this.config));
    } catch (err) {
      console.error('Failed to reload config:', err);
    }
  }
 
  onChange(fn: (config: Map<string, string>) => void) {
    this.listeners.push(fn);
  }
}

Comparison with Alternatives

FeatureK8s SecretsHashiCorp VaultAWS Secrets ManagerSealed Secrets
Encryption at RestManual configBuilt-inBuilt-inBuilt-in
Audit LoggingNoYesYesNo
Automatic RotationNoYesYesNo
GitOps CompatibleNoPartialNoYes
Multi-ClusterManual syncYesYesPer-cluster keys
CostFreeSelf-hosted or HCPPer-secret pricingFree (OSS)
ComplexityLowHighMediumLow

For simple applications, Kubernetes Secrets with encryption at rest are sufficient. For production workloads requiring audit trails, automatic rotation, and fine-grained access control, use an external secret manager. For GitOps workflows, use Sealed Secrets or External Secrets Operator to bridge the gap.

Advanced Patterns and Techniques

Secret Rotation Strategies

Secret rotation is a critical security practice that limits the blast radius of compromised credentials. Manual rotation is error-prone and causes downtime, so automate it using one of several strategies:

Strategy 1: Rolling Update with Dual Secrets

Maintain two versions of a Secret during rotation. Applications read from the active Secret while the new credential propagates:

#!/bin/bash
# rotate-db-password.sh
NEW_PASSWORD=$(openssl rand -base64 32)
 
# Create new secret version
kubectl create secret generic db-credentials-v2 \
  --from-literal=username=admin \
  --from-literal=password="$NEW_PASSWORD" \
  --dry-run=client -o yaml | kubectl apply -f -
 
# Update deployment to use new secret
kubectl set env deployment/api-server \
  DB_PASSWORD_SECRET=db-credentials-v2
 
# Wait for rollout
kubectl rollout status deployment/api-server --timeout=300s
 
# Clean up old secret
kubectl delete secret db-credentials --ignore-not-found

Strategy 2: External Secrets Operator with Automatic Refresh

The External Secrets Operator can poll external providers at a configurable interval:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: rotating-db-creds
spec:
  refreshInterval: 5m  # Check for new values every 5 minutes
  secretStoreRef:
    name: aws-sm
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        connection-string: "postgresql://{{ .username }}:{{ .password }}@db:5432/app"
  data:
  - secretKey: username
    remoteRef:
      key: prod/db/credentials
      property: username
  - secretKey: password
    remoteRef:
      key: prod/db/credentials
      property: password

Strategy 3: Stakater Reloader for Automatic Rollout

When a Secret changes, most applications won't pick up the new value until restarted. Stakater Reloader watches for Secret/ConfigMap changes and triggers rolling updates:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  annotations:
    reloader.stakater.com/auto: "true"  # Watch all referenced resources
spec:
  template:
    spec:
      containers:
      - name: api
        image: my-api:1.0
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password

Sealed Secrets for GitOps

Sealed Secrets encrypt Secret data so it can be safely stored in Git. The Sealed Secrets controller in the cluster decrypts them into regular Secrets:

# Install Sealed Secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/controller.yaml
 
# Seal a secret
kubeseal --format yaml < secret.yaml > sealed-secret.yaml

The sealed secret can only be decrypted by the controller running in the target cluster, making it safe to commit to version control.

External Secrets Operator

The External Secrets Operator synchronizes secrets from external providers into Kubernetes Secrets:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
  - secretKey: username
    remoteRef:
      key: prod/database
      property: username
  - secretKey: password
    remoteRef:
      key: prod/database
      property: password

This keeps secrets synchronized with the external provider, enabling automatic rotation without pod restarts.

Testing Strategies

Test configuration management using kubectl dry-run and validation tools:

# Validate ConfigMap before applying
kubectl create configmap test-config --from-file=config.yml --dry-run=client -o yaml | \
  kubectl apply --dry-run=server -f -
 
# Verify Secret contents (base64 decoded)
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
 
# Test configuration injection
kubectl run debug --image=busybox --rm -it --restart=Never -- \
  env | grep -E "^(DATABASE|API|LOG)"

For automated testing, validate that configuration changes propagate correctly:

import { execSync } from 'child_process';
 
describe('Configuration Management', () => {
  it('should inject ConfigMap values as environment variables', () => {
    const output = execSync(
      'kubectl exec deploy/api-server -- env'
    ).toString();
    expect(output).toContain('LOG_LEVEL=info');
    expect(output).toContain('DATABASE_HOST=postgres.default.svc.cluster.local');
  });
 
  it('should mount Secret files in the pod', () => {
    const output = execSync(
      'kubectl exec deploy/api-server -- cat /app/secrets/db-password'
    ).toString();
    expect(output).toBeTruthy();
    expect(output).not.toBe('');
  });
});

Immutable ConfigMaps and Hot Reloading

For production stability, Kubernetes supports immutable ConfigMaps and Secrets. Once marked immutable, the resource cannot be updated—you must create a new resource with a different name and update the deployment to reference it. This prevents accidental configuration changes and improves API server performance (the kubelet doesn't poll immutable resources for changes).

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v2  # Version in the name
immutable: true
data:
  DATABASE_HOST: "postgres.default.svc.cluster.local"
  LOG_LEVEL: "warn"

For applications that support hot reloading (like nginx with nginx -s reload or Prometheus with /-/reload), mount ConfigMaps as volumes and use a sidecar to detect changes:

# Reloader sidecar watches for ConfigMap changes
# and sends SIGHUP to the main container
- name: config-reloader
  image: stakater/reloader:latest
  env:
  - name: AUTO_RELOAD
    value: "true"

Stakater Reloader is a popular Kubernetes controller that watches ConfigMaps and Secrets, then performs rolling restarts of deployments when the referenced resources change. This bridges the gap between Kubernetes' eventual consistency model and applications that need immediate configuration updates.

OPA/Gatekeeper Policy Enforcement

Prevent misconfigurations before they reach the cluster using admission controllers. OPA Gatekeeper enforces policies like "all Secrets must have encryption annotations" or "ConfigMaps must follow naming conventions":

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiresecretlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequireSecretLabels
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package k8srequiresecretlabels
      violation[{"msg": msg}] {
        not input.review.object.metadata.labels["app.kubernetes.io/name"]
        msg := "Secrets must have app.kubernetes.io/name label"
      }
      violation[{"msg": msg}] {
        not input.review.object.metadata.labels["app.kubernetes.io/component"]
        msg := "Secrets must have app.kubernetes.io/component label"
      }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireSecretLabels
metadata:
  name: require-secret-labels
spec:
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Secret"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]

Multi-Cluster Secret Synchronization

Organizations running multiple clusters need consistent secret distribution. The External Secrets Operator supports ClusterSecretStore resources that are cluster-scoped, enabling centralized secret management across fleet:

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: centralized-vault
spec:
  provider:
    vault:
      server: "https://vault.example.com:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "external-secrets"
          serviceAccountRef:
            name: "external-secrets-sa"
            namespace: "external-secrets"

Each cluster's External Secrets Operator references this ClusterSecretStore, pulling secrets from the central Vault instance. This pattern works across AWS, GCP, Azure, and on-premises clusters with consistent access policies.

ConfigMap Hot-Reload with Reloader Sidecar

For applications that support dynamic configuration reloading (Nginx, Prometheus, Envoy), use a sidecar to detect ConfigMap changes and signal the main container:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-with-reloader
spec:
  template:
    metadata:
      annotations:
        configmap.reloader.stakater.com/reload: "nginx-config"
        secret.reloader.stakater.com/reload: "tls-secret"
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        volumeMounts:
        - name: config
          mountPath: /etc/nginx/conf.d
        - name: tls
          mountPath: /etc/nginx/tls
          readOnly: true
      volumes:
      - name: config
        configMap:
          name: nginx-config
      - name: tls
        secret:
          secretName: tls-secret

This pattern eliminates the need for manual kubectl rollout restart after configuration changes, enabling true GitOps workflows where a Git commit triggers automatic configuration propagation.

Future Outlook

Kubernetes configuration management is evolving toward external secret management as the default. The External Secrets Operator is becoming the standard for multi-cloud secret synchronization, supporting over 20 providers including AWS Secrets Manager, Google Secret Manager, Azure Key Vault, HashiCorp Vault, and even Kubernetes-to-Kubernetes synchronization. Confidential computing integration will enable Secrets to be processed in encrypted memory, preventing even cluster administrators from accessing plaintext values. The Gateway API's configuration model is introducing new patterns for dynamic configuration that doesn't require pod restarts.

Looking further ahead, the Kubernetes community is exploring several initiatives that will fundamentally change how we think about configuration. The ConfigConnector project from Google enables managing cloud resources as Kubernetes objects, blurring the line between application configuration and infrastructure provisioning. The CEL (Common Expression Language) integration in Kubernetes admission webhooks is enabling more sophisticated configuration validation without requiring external policy engines. And the growing adoption of WebAssembly in Kubernetes (through projects like SpinKube and wasmCloud) is creating new configuration paradigms where application configuration is compiled directly into WebAssembly modules, eliminating the need for runtime configuration injection entirely.

The convergence of GitOps, policy-as-code, and external secret management is creating a unified configuration management stack where all changes flow through Git, are validated by policy engines, and are synchronized by operators. This stack eliminates the manual kubectl apply commands that are the source of most configuration-related incidents and replaces them with auditable, reviewable, and automatically validated configuration changes.

Conclusion

Kubernetes ConfigMaps and Secrets provide the foundation for application configuration management, but production deployments require additional security measures. Enable encryption at rest, implement RBAC, and consider external secret managers for sensitive data. Use immutable ConfigMaps for static configuration and implement hot-reloading for dynamic updates. The tools and patterns described in this guide form a comprehensive configuration management strategy that scales from development to enterprise production environments. Start with the basics—proper Secret encryption and RBAC—then gradually adopt advanced patterns like external secret managers and GitOps workflows as your infrastructure matures.

Key takeaways: Secrets are base64-encoded, not encrypted—always enable encryption at rest. Use volume mounts instead of environment variables for configuration that changes at runtime. For GitOps workflows, use Sealed Secrets or External Secrets Operator. Never commit plaintext secrets to version control. Implement immutable ConfigMaps for production stability and use Stakater Reloader or similar controllers for automatic rollout restarts on configuration changes. Version your configuration resources by appending suffixes to their names, enabling instant rollback to previous configurations without waiting for new deployments.

Key takeaways: Secrets are base64-encoded, not encrypted—always enable encryption at rest. Use volume mounts instead of environment variables for configuration that changes at runtime. For GitOps workflows, use Sealed Secrets or External Secrets Operator. Never commit plaintext secrets to version control. Implement immutable ConfigMaps for production stability and use Stakater Reloader or similar controllers for automatic rollout restarts on configuration changes.

For further reading, consult the Kubernetes Secrets documentation, the External Secrets Operator documentation, and the HashiCorp Vault Kubernetes integration guide. Understanding these patterns early prevents costly retrofitting when security requirements inevitably increase.


Configuration management