All Articles

What is a ConfigMap and Secret in Kubernetes? Explained Simply (2026)

ConfigMaps and Secrets separate configuration from code in Kubernetes. Here's what they are, how they work, and when to use each one — explained simply.

DevOpsBoysApr 13, 20264 min read
Share:Tweet

Hardcoding config inside a Docker image is a bad idea — you'd need to rebuild the image just to change a port number. ConfigMaps and Secrets solve this by separating configuration from the container.

The Problem They Solve

dockerfile
# BAD — config baked into image
ENV DATABASE_URL=postgres://prod-db:5432/myapp
ENV API_KEY=sk-1234secretkey

If you change the DB URL or rotate the API key, you rebuild and redeploy the entire image. ConfigMaps and Secrets let you change config without touching the image.


What is a ConfigMap?

A ConfigMap stores non-sensitive configuration data as key-value pairs. Think of it as a config file that lives in Kubernetes.

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  DATABASE_HOST: "postgres-service"
  DATABASE_PORT: "5432"
  LOG_LEVEL: "info"
  APP_ENV: "production"
  config.yaml: |
    server:
      port: 8080
      timeout: 30s
    database:
      pool_size: 10

ConfigMaps can store:

  • Simple key-value pairs (LOG_LEVEL: info)
  • Entire config files (config.yaml: |...)
  • Multi-line strings

What NOT to put in ConfigMap: passwords, tokens, API keys — use Secret for those.


What is a Secret?

A Secret stores sensitive data — passwords, API keys, TLS certificates, SSH keys.

yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: production
type: Opaque
data:
  DB_PASSWORD: cG9zdGdyZXNwYXNzd29yZA==    # base64 encoded
  API_KEY: c2stMTIzNHNlY3JldGtleQ==

Important: Secrets are base64 encoded, NOT encrypted by default. Base64 is not security — it's just encoding. Enable encryption at rest in etcd for real security.

bash
# Encode a value
echo -n "mypassword" | base64
# bXlwYXNzd29yZA==
 
# Decode a value
echo "bXlwYXNzd29yZA==" | base64 --decode
# mypassword

How to Create Them

ConfigMap

bash
# From literal values
kubectl create configmap app-config \
  --from-literal=DB_HOST=postgres \
  --from-literal=LOG_LEVEL=info \
  -n production
 
# From a file
kubectl create configmap nginx-config \
  --from-file=nginx.conf \
  -n production
 
# From a directory (creates one key per file)
kubectl create configmap app-configs \
  --from-file=configs/

Secret

bash
# From literal values (kubectl handles base64 automatically)
kubectl create secret generic app-secrets \
  --from-literal=DB_PASSWORD=mypassword \
  --from-literal=API_KEY=sk-1234 \
  -n production
 
# From files (e.g., TLS certificates)
kubectl create secret tls my-tls \
  --cert=tls.crt \
  --key=tls.key
 
# Docker registry credentials
kubectl create secret docker-registry regcred \
  --docker-server=ghcr.io \
  --docker-username=myuser \
  --docker-password=mytoken

How to Use Them in Pods

Method 1: Environment Variables

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: my-app:latest
        env:
        # Single key from ConfigMap
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: LOG_LEVEL
 
        # Single key from Secret
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: DB_PASSWORD
 
        # All keys from ConfigMap as env vars
        envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: app-secrets

Method 2: Volume Mounts (for config files)

yaml
spec:
  containers:
  - name: app
    image: my-app:latest
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config      # files appear here
    - name: secret-volume
      mountPath: /etc/secrets
      readOnly: true              # always readOnly for secrets
 
  volumes:
  - name: config-volume
    configMap:
      name: app-config
 
  - name: secret-volume
    secret:
      secretName: app-secrets
      defaultMode: 0400           # restrictive permissions

When mounted as volumes, each key becomes a file:

/etc/config/DATABASE_HOST    → contains "postgres-service"
/etc/config/LOG_LEVEL        → contains "info"
/etc/config/config.yaml      → contains the full YAML

ConfigMap vs Secret: When to Use Which

DataUse
Database hostnameConfigMap
Database portConfigMap
Database passwordSecret
API endpoint URLConfigMap
API key / tokenSecret
Log levelConfigMap
TLS certificateSecret
Feature flagsConfigMap
SSH private keySecret
App config fileConfigMap

Rule: If you'd be embarrassed for it to be logged or visible in kubectl describe — use Secret.


Watching for Updates

ConfigMaps and Secrets mounted as volumes update automatically (within ~1 minute) when you change them. Environment variables do NOT update — the pod must restart.

bash
# Update a ConfigMap
kubectl edit configmap app-config -n production
# or
kubectl create configmap app-config --from-literal=LOG_LEVEL=debug \
  --dry-run=client -o yaml | kubectl apply -f -
 
# Force pod restart to pick up env var changes
kubectl rollout restart deployment/my-app -n production

Immutable ConfigMaps and Secrets

For production configs that shouldn't change (set and forget), add immutable: true. This also improves performance — Kubernetes stops watching for changes.

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v2
immutable: true    # ← cannot be edited, must delete and recreate
data:
  APP_VERSION: "2.1.0"

Viewing and Debugging

bash
# List ConfigMaps
kubectl get configmaps -n production
 
# View contents
kubectl describe configmap app-config -n production
kubectl get configmap app-config -o yaml -n production
 
# List Secrets (values are hidden)
kubectl get secrets -n production
kubectl describe secret app-secrets -n production
 
# Decode a secret value
kubectl get secret app-secrets -o jsonpath='{.data.DB_PASSWORD}' | base64 --decode
 
# Check if pod is using the right ConfigMap
kubectl describe pod <pod-name> -n production | grep -A5 "Environment\|Mounts"

Best Practices

Never commit Secrets to Git — use Sealed Secrets, External Secrets Operator, or Vault
Enable etcd encryption at rest — Secrets aren't encrypted by default
Use RBAC to restrict Secret access — not everyone needs to read production secrets
Separate configs per environmentapp-config-dev, app-config-prod
Use immutable: true for stable production configs
Mount secrets as volumes instead of env vars — easier to rotate without restart


Summary

ConfigMapSecret
ForNon-sensitive configSensitive data
StoragePlain textBase64 encoded
Encrypted at restNo (by default)No (by default, enable separately)
Visible in describeYesNo (values hidden)
Use asEnv vars or volumeEnv vars or volume

ConfigMaps and Secrets are the standard Kubernetes way to externalize configuration. Master them and you'll never bake config into your images again.


Learn More

Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments