ConfigMaps and Secrets: Managing Application Configuration in Kubernetes

Inject configuration data and sensitive information into Kubernetes pods using ConfigMaps and Secrets. Learn about mounting strategies, environment variables, and security best practices.

published: reading time: 9 min read

ConfigMaps and Secrets: Managing Application Configuration in Kubernetes

Applications need configuration. Database URLs, feature flags, API keys, and connection strings change between environments. Hardcoding these values into container images defeats the purpose of containerization. Kubernetes provides two resources for injecting configuration into pods: ConfigMap and Secret.

This post explains how to create these resources, inject them into pods, and use them securely.

For Kubernetes basics, see the Kubernetes fundamentals post. For advanced patterns like external secret operators, read the Advanced Kubernetes post.

When to Use / When Not to Use

Use ConfigMaps when

ConfigMaps work well for non-sensitive configuration that changes between environments: feature flags, environment variables, application settings, and configuration files like nginx.conf or config.yaml.

Share ConfigMaps across pods in the same namespace. The key benefit is keeping configuration out of container images so the same image works in dev, staging, and prod.

Use Secrets when

Secrets are for anything that should not appear in plaintext: database passwords, API keys, OAuth tokens, TLS certificates, SSH keys.

Kubernetes stores Secrets as base64-encoded strings in etcd. Without encryption at rest, these are readable by anyone with etcd access. For sensitive production workloads, use External Secret Operators that sync from Vault, AWS Secrets Manager, or similar.

Skip ConfigMaps for

Sensitive data. It is not encrypted by default. If your ConfigMap name ends in _secret or contains passwords, use a Secret instead.

Skip native Kubernetes Secrets for (without encryption at rest)

Highly regulated environments (PCI-DSS, HIPAA) and multi-tenant clusters where you need strict isolation. Enable encryption at rest before using Secrets for anything genuinely sensitive.

ConfigMap/Secret Injection Flow

flowchart TD
    CM[ConfigMap<br/>or Secret] -->|kubectl apply| K8s[Kubernetes API Server]
    K8s -->|Store| etcd[etcd datastore]
    K8s -->|Watch| Pod[Pod with Container]
    Pod -->|Mount as| Volume[/etc/config<br/>or /run/secrets]
    Pod -->|Inject as| EnvVar[Environment<br/>Variables]

Volume mounts update automatically when the ConfigMap or Secret changes. Environment variables do not update after pod start. For dynamic configuration updates, use volume mounts with application-side file watching or SIGHUP reload.

ConfigMap Creation and Usage Patterns

ConfigMaps store non-sensitive configuration as key-value pairs or files. You create them from literal values, files, or directories.

Creating from literals

kubectl create configmap app-config \
  --from-literal=ENVIRONMENT=production \
  --from-literal=LOG_LEVEL=info \
  --from-literal=MAX_CONNECTIONS=100

Creating from a file

kubectl create configmap nginx-config \
  --from-file=nginx.conf

The filename becomes the configmap key. The file contents become the value.

Creating from a directory

kubectl create configmap html-content \
  --from-file=html/

Defining in YAML

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  ENVIRONMENT: "production"
  LOG_LEVEL: "info"
  DATABASE_HOST: "postgres.database.svc.cluster.local"
  DATABASE_PORT: "5432"

Injecting ConfigMap into Pods

You can inject ConfigMap values as environment variables or volume mounts.

Environment variables

apiVersion: v1
kind: Pod
metadata:
  name: web-app
  namespace: production
spec:
  containers:
    - name: web-app
      image: nginx:1.25
      envFrom:
        - configMapRef:
            name: app-config

With envFrom, all keys from the ConfigMap become environment variables. For selective injection, use env:

env:
  - name: LOG_LEVEL
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: LOG_LEVEL
  - name: ENVIRONMENT
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: ENVIRONMENT

Volume mounts

Mount ConfigMap values as files:

apiVersion: v1
kind: Pod
metadata:
  name: web-app
spec:
  containers:
    - name: web-app
      image: nginx:1.25
      volumeMounts:
        - name: config
          mountPath: /etc/nginx/conf.d
          readOnly: true
  volumes:
    - name: config
      configMap:
        name: nginx-config
        items:
          - key: nginx.conf
            path: default.conf

With this setup, /etc/nginx/conf.d/default.conf contains the contents of the nginx.conf key from the ConfigMap.

Secrets Types and Limitations

Secrets work like ConfigMaps but store sensitive data. Kubernetes stores Secrets as base64-encoded strings, not encrypted. Anyone with API access or etcd access can read Secrets.

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: production
type: Opaque
data:
  username: cG9zdGdyZXM=
  password: c2VjcmV0cGFzc3dvcmQ=

Generate base64 values:

echo -n "postgres" | base64
echo -n "secretpassword" | base64

Kubernetes Secret types

TypeUse Case
OpaqueArbitrary key-value pairs
kubernetes.io/tlsTLS certificates
kubernetes.io/dockerconfigjsonDocker registry credentials
kubernetes.io/basic-authBasic auth credentials

TLS Secret

apiVersion: v1
kind: Secret
metadata:
  name: tls-cert
  namespace: production
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
  tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...

Mounting vs Environment Variables

Both approaches work, but they have different properties:

AspectEnvironment VariablesVolume Mounts
Update propagationPod must restartFiles update automatically (with sync wave)
VisibilityVisible in kubectl exec outputLess exposed
FormatSingle valuesFiles
Size limitNot suitable for large configsGood for config files

Environment variables work well for simple string values. Volume mounts are better for configuration files, certificates, and other structured data.

For configuration files that change, volume mounts update automatically. Kubernetes syncs the ConfigMap to the volume within a short delay (configurable via ConfigMap data section). Your application needs to watch for file changes or reload on SIGHUP.

External Secret Operators

Kubernetes Secrets have limitations. They are not encrypted by default, they have strict size limits (1MB), and they do not integrate with external secret managers. External Secret Operators bridge this gap.

Tools like External Secrets Operator (ESO), HashiCorp Vault, and AWS Secrets Manager integrate with Kubernetes to sync secrets from external stores.

External Secrets Operator example

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-secrets
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-secrets
    creationPolicy: Owner
  data:
    - secretKey: password
      remoteRef:
        key: production/database
        property: password

This ExternalSecret syncs the password field from the Vault key production/database into a Secret named db-secrets. The Operator handles rotation and updates.

ESO supports AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault, and other providers.

Security Hardening for Secrets

Since Kubernetes does not encrypt Secrets by default, you need additional measures for sensitive data:

Enable encryption at rest

Configure the kube-apiserver to encrypt Secrets before storing them in etcd:

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

Enable this in the kube-apiserver configuration:

--encryption-provider-config=/path/to/encryption-config

Use RBAC to restrict Secret access

Limit who can read Secrets using Role-Based Access Control:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: secret-reader
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]

Enable RBAC authentication

Ensure your kube-apiserver has RBAC enabled:

kubectl get pod kube-apiserver -n kube-system -o jsonpath='{.spec.containers[0].command}' | grep -i rbac

Consider third-party solutions

For production workloads with strict security requirements, use a secrets manager with a Kubernetes operator. Vault, AWS Secrets Manager, and similar tools provide audit logs, secret rotation, and encryption that Kubernetes-native Secrets lack.

Conclusion

ConfigMaps and Secrets let you decouple configuration from container images. ConfigMaps handle non-sensitive configuration like environment settings and config files. Secrets store credentials, tokens, and certificates but do not provide encryption by default.

Mount ConfigMaps as volumes for configuration files and as environment variables for simple values. Use External Secret Operators to integrate with external secret managers for production workloads. Enable encryption at rest for Secrets and restrict access with RBAC.

Getting configuration right makes your applications portable across environments and easier to manage at scale. For advanced Kubernetes patterns including custom controllers and operators, see the Advanced Kubernetes post.

Production Failure Scenarios

Secret Not Found at Pod Startup

If a Secret referenced in a pod does not exist when the pod starts, the pod fails to start with SecretNotFound error.

Symptoms: Pod stuck in Pending or ContainerCreating state, Secrets not found in events.

Diagnosis:

kubectl describe pod <pod-name> -n <namespace>
kubectl get secret <secret-name> -n <namespace>

Mitigation: Create Secrets before applying Pods that reference them. Use depends_on in Helm or kustomize to enforce ordering.

ConfigMap Update Not Propagating to Pods

When a ConfigMap is updated, volume-mounted files reflect the change, but environment variables do not update without pod restart.

Symptoms: Application uses stale configuration even after ConfigMap update.

Mitigation: Use volume mounts for configuration that changes at runtime. Implement SIGHUP handling in your application to reload on file change, or restart pods automatically using a tool like Reloader.

Base64 Encoding Errors in Secrets

Kubernetes Secrets store values as base64 strings. If the base64 encoding is wrong (missing padding, wrong characters), the decoded value is corrupted.

Symptoms: Application fails to connect to database with authentication failed, decoded values are garbled.

Mitigation: Always use echo -n "value" | base64 (note the -n to avoid trailing newline) and echo "b64value" | base64 -d for decoding.

Anti-Patterns

Storing Secrets in ConfigMaps

Never store actual secrets (passwords, API keys) in ConfigMaps. ConfigMaps are not encrypted by default and are visible in YAML exports and etcd dumps. Always use Secret resources for sensitive data.

Not Using binaryData for Non-UTF8 Content

ConfigMaps store data as UTF-8 strings. For binary files (certificates, keys), use the binaryData field instead of data and base64-encode the content.

Copying Entire ConfigMap into Every Environment

Different environments (dev, staging, prod) need different configurations. Copying the same ConfigMap across environments leads to hardcoded production URLs in dev or debug flags in production.

Use Kustomize overlays or Helm values to inject environment-specific configuration.

Quick Recap Checklist

Use this checklist when managing ConfigMaps and Secrets:

  • Used ConfigMaps for non-sensitive configuration, Secrets for credentials and keys
  • Mounted ConfigMaps as volumes for configuration files and dynamic updates
  • Injected ConfigMap values as environment variables only for static configuration
  • Enabled encryption at rest for etcd to protect Secret data
  • Used RBAC to restrict who can read Secrets
  • Used External Secret Operators for production workloads requiring audit trails
  • Tested application behavior when ConfigMap or Secret values change at runtime
  • Used binaryData field for non-UTF8 secret content
  • Never committed Secret values to version control
  • Used depends_on or ordering tools to ensure Secrets exist before Pods start

Category

Related Posts

Kustomize: Native Kubernetes Configuration Management

Use Kustomize for declarative Kubernetes configuration management without Helm's templating—overlays, patches, and environment-specific customization.

#kubernetes #kustomize #devops

Container Security: Image Scanning and Vulnerability Management

Implement comprehensive container security: from scanning images for vulnerabilities to runtime security monitoring and secrets protection.

#container-security #docker #kubernetes

Deployment Strategies: Rolling, Blue-Green, and Canary Releases

Compare and implement deployment strategies—rolling updates, blue-green deployments, and canary releases—to reduce risk and enable safe production releases.

#deployment #devops #kubernetes