CloudOpsGuide
kubernetes

Kubernetes ConfigMaps and Secrets: Best Practices

Intermediate
15 minutes
October 2026
CloudOpsGuide Team

Kubernetes ConfigMaps and Secrets: Best Practices

Complete guide to configuration management in Kubernetes — ConfigMaps for plain data, Secrets for sensitive data, and when to use which.

Table of Contents

ConfigMap vs Secret

ConfigMapSecret
DataPlain text, non-sensitiveBase64-encoded, sensitive
Size limit1 MiB1 MiB
EncryptionNot encrypted at restCan be encrypted at rest (encryption config)
Use forConfig files, env vars, CLI argsPasswords, tokens, keys, certs
SecurityAnyone can readRBAC can restrict access

Rule: If it shouldn't be in a README, it goes in a Secret.

Creating ConfigMaps

From Literal Values

kubectl create configmap app-config \
  --from-literal=LOG_LEVEL=debug \
  --from-literal=MAX_CONNECTIONS=100 \
  --from-literal=FEATURE_FLAG_X=true

From a File

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

From a Directory

kubectl create configmap app-config-dir \
  --from-file=./config/

YAML Manifest

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "debug"
  MAX_CONNECTIONS: "100"
  database.conf: |
    host = db.example.com
    port = 5432
    pool_size = 10

Creating Secrets

Generic Secret

kubectl create secret generic db-creds \
  --from-literal=username=admin \
  --from-literal=password='S!B*d$zDsb='

Docker Registry Secret

kubectl create secret docker-registry reg-cred \
  --docker-server=registry.example.com \
  --docker-username=user \
  --docker-password=pass

TLS Secret

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

YAML Manifest

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  username: YWRtaW4=    # base64("admin")
  password: UyFCKmQkenk=   # base64 — run `echo -n 'pass' | base64`

Using in Pods

As Environment Variables

apiVersion: v1
kind: Pod
spec:
  containers:
    - name: app
      image: myapp
      env:
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: LOG_LEVEL
        - name: DB_USER
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: username
        - name: DB_PASS
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password

All Keys at Once (envFrom)

envFrom:
  - configMapRef:
      name: app-config
  - secretRef:
      name: db-secret

Every key in the ConfigMap/Secret becomes an env var.

As Mounted Files

spec:
  containers:
    - name: app
      volumeMounts:
        - name: config
          mountPath: /etc/config
        - name: secrets
          mountPath: /etc/secrets
          readOnly: true
  volumes:
    - name: config
      configMap:
        name: app-config
    - name: secrets
      secret:
        secretName: db-secret
        defaultMode: 0400

Each key becomes a file: /etc/config/LOG_LEVEL, /etc/secrets/username, etc.

Best Practices

1. Namespace Secrets per Environment

# Don't share secrets across environments
metadata:
  name: db-secret
  namespace: production    # separate from dev/staging

2. Use RBAC to Restrict Access

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["db-secret"]    # only this secret
  verbs: ["get"]

3. Enable Encryption at Rest

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

4. Never Commit Secrets

# Don't do this in git
data:
  password: UyFCKmQkenk=    # committed to repo = leaked

Use external secret stores or sealed secrets instead.

5. Use immutable ConfigMaps for Large Configs

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
immutable: true    # prevents accidental changes
data:
  nginx.conf: |
    ...

Common Issues

Secret Shows as "***" in kubectl

# Base64 is not encryption! Decode easily:
kubectl get secret db-secret -o jsonpath='{.data.password}' | base64 -d

This is expected — Base64 is encoding, not encryption. The protection comes from RBAC + encryption-at-rest.

ConfigMap Changes Not Reflected

ConfigMap updates don't auto-update env vars in running pods. You must restart pods:

kubectl rollout restart deployment/my-app

Mounted volumes DO update automatically (with a delay of ~1-2 minutes).

"Secret not found" on mount

kubectl describe pod <pod>
# Events: MountVolume.SetUp failed for volume "secrets" : secret "db-secret" not found
# → secret doesn't exist in the pod's namespace

External Secret Management

For production, consider external secret stores:

ToolPurpose
External Secrets OperatorSyncs from AWS SM, Azure KV, Vault, GCP SM
Sealed SecretsEncrypts secrets into git-safe files
HashiCorp VaultFull secret lifecycle management
# External Secrets Operator example
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-secret
spec:
  secretStoreRef:
    name: azure-keyvault
    kind: SecretStore
  target:
    name: db-secret
  data:
  - secretKey: password
    remoteRef:
      key: database-password

Related Articles


Last Updated: October 2026
Author: CloudOpsGuide Team
Difficulty: Intermediate
Estimated Reading Time: 15 minutes