Skip to main content
Kubernetes intermediate Lesson 6 of 8

ConfigMaps, Secrets, and Storage

Learn how Kubernetes separates configuration from images using ConfigMaps/Secrets and how volumes persist data.

ConfigMaps

A ConfigMap stores non-sensitive configuration as key-value pairs, keeping config out of your container images.

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_ENV: "production"
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"
  config.json: |
    {
      "featureFlags": {
        "darkMode": true,
        "betaApi": false
      }
    }
kubectl apply -f configmap.yaml

# View the configmap
kubectl get configmap app-config
kubectl describe configmap app-config

# Create a configmap directly from a file
kubectl create configmap nginx-conf --from-file=nginx.conf

# Create from literal values
kubectl create configmap db-config --from-literal=DB_HOST=postgres --from-literal=DB_PORT=5432

Using ConfigMap as Environment Variables

spec:
  containers:
    - name: app
      image: my-api:1.0
      envFrom:
        - configMapRef:
            name: app-config     # inject all keys as env vars
      env:
        - name: SPECIFIC_KEY     # inject a single key
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: LOG_LEVEL

Using ConfigMap as a Mounted File

spec:
  volumes:
    - name: config-vol
      configMap:
        name: app-config

  containers:
    - name: app
      image: my-api:1.0
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config   # each key becomes a file
# Inside the container, files appear at /etc/config/APP_ENV, etc.
kubectl exec -it <pod-name> -- ls /etc/config

Secrets

A Secret stores sensitive data (passwords, tokens, certificates) encoded in base64.

# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  DB_PASSWORD: cGFzc3dvcmQxMjM=   # base64("password123")
  API_KEY: c3VwZXJzZWNyZXQ=       # base64("supersecret")
# Encode a value to base64
echo -n "password123" | base64

# Decode a secret value
kubectl get secret db-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 --decode

# Create a secret imperatively (avoids storing plaintext in YAML)
kubectl create secret generic db-secret \
  --from-literal=DB_PASSWORD=password123 \
  --from-literal=API_KEY=supersecret

# Create a TLS secret from cert files
kubectl create secret tls my-tls \
  --cert=tls.crt \
  --key=tls.key

Using Secrets in Pods

spec:
  containers:
    - name: app
      image: my-api:1.0
      envFrom:
        - secretRef:
            name: db-secret      # inject all secret keys as env vars
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: DB_PASSWORD

Volumes and Persistent Storage

emptyDir — Temporary Shared Storage

Lives as long as the Pod exists. Great for sharing files between sidecar containers.

spec:
  volumes:
    - name: scratch
      emptyDir: {}

  containers:
    - name: writer
      image: busybox
      command: ["sh", "-c", "echo hello > /data/msg; sleep 3600"]
      volumeMounts:
        - name: scratch
          mountPath: /data

    - name: reader
      image: busybox
      command: ["sh", "-c", "cat /data/msg; sleep 3600"]
      volumeMounts:
        - name: scratch
          mountPath: /data

PersistentVolume and PersistentVolumeClaim

For data that must survive Pod restarts or rescheduling.

# pvc.yaml — request storage (you don't manage where it comes from)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
spec:
  accessModes:
    - ReadWriteOnce        # one node can read/write at a time
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard   # use "standard" for minikube
# Use the PVC in a Deployment
spec:
  volumes:
    - name: postgres-data
      persistentVolumeClaim:
        claimName: postgres-pvc   # reference the PVC

  containers:
    - name: postgres
      image: postgres:15
      env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: DB_PASSWORD
      volumeMounts:
        - name: postgres-data
          mountPath: /var/lib/postgresql/data
kubectl apply -f pvc.yaml

# Check PVC status (Bound = storage allocated)
kubectl get pvc
kubectl describe pvc postgres-pvc

# Check the underlying PersistentVolume
kubectl get pv

Access Modes

ModeAbbreviationMeaning
ReadWriteOnceRWOSingle node read/write
ReadOnlyManyROXMultiple nodes read-only
ReadWriteManyRWXMultiple nodes read/write

Complete Database Deployment Example

# postgres-full.yaml
---
apiVersion: v1
kind: Secret
metadata:
  name: pg-secret
data:
  POSTGRES_PASSWORD: cGFzc3dvcmQ=   # "password"

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: pg-config
data:
  POSTGRES_DB: myapp
  POSTGRES_USER: appuser

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pg-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: pg-pvc
      containers:
        - name: postgres
          image: postgres:15
          envFrom:
            - configMapRef:
                name: pg-config
            - secretRef:
                name: pg-secret
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
          ports:
            - containerPort: 5432
kubectl apply -f postgres-full.yaml
kubectl get pods,pvc,svc

Learning Outcomes

You can:

  • Create ConfigMaps and consume them as env vars or mounted files
  • Store sensitive data in Secrets and reference them securely in Pods
  • Choose the right volume type (emptyDir vs PVC) for your use case
  • Write a complete stateful workload with config, secrets, and persistent storage

Frequently Asked Questions

When should I use a ConfigMap vs a Secret?
Use ConfigMaps for non-sensitive configuration (app settings, feature flags). Use Secrets for sensitive data (passwords, tokens, certificates). Secrets are base64-encoded by default — add encryption-at-rest for production.
What happens to data in an emptyDir volume when a pod restarts?
emptyDir data survives container restarts within the same pod, but is deleted when the pod is removed from a node. Use a PersistentVolume for data that must survive pod deletion.